tinymce.js 920 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914209152091620917209182091920920209212092220923209242092520926209272092820929209302093120932209332093420935209362093720938209392094020941209422094320944209452094620947209482094920950209512095220953209542095520956209572095820959209602096120962209632096420965209662096720968209692097020971209722097320974209752097620977209782097920980209812098220983209842098520986209872098820989209902099120992209932099420995209962099720998209992100021001210022100321004210052100621007210082100921010210112101221013210142101521016210172101821019210202102121022210232102421025210262102721028210292103021031210322103321034210352103621037210382103921040210412104221043210442104521046210472104821049210502105121052210532105421055210562105721058210592106021061210622106321064210652106621067210682106921070210712107221073210742107521076210772107821079210802108121082210832108421085210862108721088210892109021091210922109321094210952109621097210982109921100211012110221103211042110521106211072110821109211102111121112211132111421115211162111721118211192112021121211222112321124211252112621127211282112921130211312113221133211342113521136211372113821139211402114121142211432114421145211462114721148211492115021151211522115321154211552115621157211582115921160211612116221163211642116521166211672116821169211702117121172211732117421175211762117721178211792118021181211822118321184211852118621187211882118921190211912119221193211942119521196211972119821199212002120121202212032120421205212062120721208212092121021211212122121321214212152121621217212182121921220212212122221223212242122521226212272122821229212302123121232212332123421235212362123721238212392124021241212422124321244212452124621247212482124921250212512125221253212542125521256212572125821259212602126121262212632126421265212662126721268212692127021271212722127321274212752127621277212782127921280212812128221283212842128521286212872128821289212902129121292212932129421295212962129721298212992130021301213022130321304213052130621307213082130921310213112131221313213142131521316213172131821319213202132121322213232132421325213262132721328213292133021331213322133321334213352133621337213382133921340213412134221343213442134521346213472134821349213502135121352213532135421355213562135721358213592136021361213622136321364213652136621367213682136921370213712137221373213742137521376213772137821379213802138121382213832138421385213862138721388213892139021391213922139321394213952139621397213982139921400214012140221403214042140521406214072140821409214102141121412214132141421415214162141721418214192142021421214222142321424214252142621427214282142921430214312143221433214342143521436214372143821439214402144121442214432144421445214462144721448214492145021451214522145321454214552145621457214582145921460214612146221463214642146521466214672146821469214702147121472214732147421475214762147721478214792148021481214822148321484214852148621487214882148921490214912149221493214942149521496214972149821499215002150121502215032150421505215062150721508215092151021511215122151321514215152151621517215182151921520215212152221523215242152521526215272152821529215302153121532215332153421535215362153721538215392154021541215422154321544215452154621547215482154921550215512155221553215542155521556215572155821559215602156121562215632156421565215662156721568215692157021571215722157321574215752157621577215782157921580215812158221583215842158521586215872158821589215902159121592215932159421595215962159721598215992160021601216022160321604216052160621607216082160921610216112161221613216142161521616216172161821619216202162121622216232162421625216262162721628216292163021631216322163321634216352163621637216382163921640216412164221643216442164521646216472164821649216502165121652216532165421655216562165721658216592166021661216622166321664216652166621667216682166921670216712167221673216742167521676216772167821679216802168121682216832168421685216862168721688216892169021691216922169321694216952169621697216982169921700217012170221703217042170521706217072170821709217102171121712217132171421715217162171721718217192172021721217222172321724217252172621727217282172921730217312173221733217342173521736217372173821739217402174121742217432174421745217462174721748217492175021751217522175321754217552175621757217582175921760217612176221763217642176521766217672176821769217702177121772217732177421775217762177721778217792178021781217822178321784217852178621787217882178921790217912179221793217942179521796217972179821799218002180121802218032180421805218062180721808218092181021811218122181321814218152181621817218182181921820218212182221823218242182521826218272182821829218302183121832218332183421835218362183721838218392184021841218422184321844218452184621847218482184921850218512185221853218542185521856218572185821859218602186121862218632186421865218662186721868218692187021871218722187321874218752187621877218782187921880218812188221883218842188521886218872188821889218902189121892218932189421895218962189721898218992190021901219022190321904219052190621907219082190921910219112191221913219142191521916219172191821919219202192121922219232192421925219262192721928219292193021931219322193321934219352193621937219382193921940219412194221943219442194521946219472194821949219502195121952219532195421955219562195721958219592196021961219622196321964219652196621967219682196921970219712197221973219742197521976219772197821979219802198121982219832198421985219862198721988219892199021991219922199321994219952199621997219982199922000220012200222003220042200522006220072200822009220102201122012220132201422015220162201722018220192202022021220222202322024220252202622027220282202922030220312203222033220342203522036220372203822039220402204122042220432204422045220462204722048220492205022051220522205322054220552205622057220582205922060220612206222063220642206522066220672206822069220702207122072220732207422075220762207722078220792208022081220822208322084220852208622087220882208922090220912209222093220942209522096220972209822099221002210122102221032210422105221062210722108221092211022111221122211322114221152211622117221182211922120221212212222123221242212522126221272212822129221302213122132221332213422135221362213722138221392214022141221422214322144221452214622147221482214922150221512215222153221542215522156221572215822159221602216122162221632216422165221662216722168221692217022171221722217322174221752217622177221782217922180221812218222183221842218522186221872218822189221902219122192221932219422195221962219722198221992220022201222022220322204222052220622207222082220922210222112221222213222142221522216222172221822219222202222122222222232222422225222262222722228222292223022231222322223322234222352223622237222382223922240222412224222243222442224522246222472224822249222502225122252222532225422255222562225722258222592226022261222622226322264222652226622267222682226922270222712227222273222742227522276222772227822279222802228122282222832228422285222862228722288222892229022291222922229322294222952229622297222982229922300223012230222303223042230522306223072230822309223102231122312223132231422315223162231722318223192232022321223222232322324223252232622327223282232922330223312233222333223342233522336223372233822339223402234122342223432234422345223462234722348223492235022351223522235322354223552235622357223582235922360223612236222363223642236522366223672236822369223702237122372223732237422375223762237722378223792238022381223822238322384223852238622387223882238922390223912239222393223942239522396223972239822399224002240122402224032240422405224062240722408224092241022411224122241322414224152241622417224182241922420224212242222423224242242522426224272242822429224302243122432224332243422435224362243722438224392244022441224422244322444224452244622447224482244922450224512245222453224542245522456224572245822459224602246122462224632246422465224662246722468224692247022471224722247322474224752247622477224782247922480224812248222483224842248522486224872248822489224902249122492224932249422495224962249722498224992250022501225022250322504225052250622507225082250922510225112251222513225142251522516225172251822519225202252122522225232252422525225262252722528225292253022531225322253322534225352253622537225382253922540225412254222543225442254522546225472254822549225502255122552225532255422555225562255722558225592256022561225622256322564225652256622567225682256922570225712257222573225742257522576225772257822579225802258122582225832258422585225862258722588225892259022591225922259322594225952259622597225982259922600226012260222603226042260522606226072260822609226102261122612226132261422615226162261722618226192262022621226222262322624226252262622627226282262922630226312263222633226342263522636226372263822639226402264122642226432264422645226462264722648226492265022651226522265322654226552265622657226582265922660226612266222663226642266522666226672266822669226702267122672226732267422675226762267722678226792268022681226822268322684226852268622687226882268922690226912269222693226942269522696226972269822699227002270122702227032270422705227062270722708227092271022711227122271322714227152271622717227182271922720227212272222723227242272522726227272272822729227302273122732227332273422735227362273722738227392274022741227422274322744227452274622747227482274922750227512275222753227542275522756227572275822759227602276122762227632276422765227662276722768227692277022771227722277322774227752277622777227782277922780227812278222783227842278522786227872278822789227902279122792227932279422795227962279722798227992280022801228022280322804228052280622807228082280922810228112281222813228142281522816228172281822819228202282122822228232282422825228262282722828228292283022831228322283322834228352283622837228382283922840228412284222843228442284522846228472284822849228502285122852228532285422855228562285722858228592286022861228622286322864228652286622867228682286922870228712287222873228742287522876228772287822879228802288122882228832288422885228862288722888228892289022891228922289322894228952289622897228982289922900229012290222903229042290522906229072290822909229102291122912229132291422915229162291722918229192292022921229222292322924229252292622927229282292922930229312293222933229342293522936229372293822939229402294122942229432294422945229462294722948229492295022951229522295322954229552295622957229582295922960229612296222963229642296522966229672296822969229702297122972229732297422975229762297722978229792298022981229822298322984229852298622987229882298922990229912299222993229942299522996229972299822999230002300123002230032300423005230062300723008230092301023011230122301323014230152301623017230182301923020230212302223023230242302523026230272302823029230302303123032230332303423035230362303723038230392304023041230422304323044230452304623047230482304923050230512305223053230542305523056230572305823059230602306123062230632306423065230662306723068230692307023071230722307323074230752307623077230782307923080230812308223083230842308523086230872308823089230902309123092230932309423095230962309723098230992310023101231022310323104231052310623107231082310923110231112311223113231142311523116231172311823119231202312123122231232312423125231262312723128231292313023131231322313323134231352313623137231382313923140231412314223143231442314523146231472314823149231502315123152231532315423155231562315723158231592316023161231622316323164231652316623167231682316923170231712317223173231742317523176231772317823179231802318123182231832318423185231862318723188231892319023191231922319323194231952319623197231982319923200232012320223203232042320523206232072320823209232102321123212232132321423215232162321723218232192322023221232222322323224232252322623227232282322923230232312323223233232342323523236232372323823239232402324123242232432324423245232462324723248232492325023251232522325323254232552325623257232582325923260232612326223263232642326523266232672326823269232702327123272232732327423275232762327723278232792328023281232822328323284232852328623287232882328923290232912329223293232942329523296232972329823299233002330123302233032330423305233062330723308233092331023311233122331323314233152331623317233182331923320233212332223323233242332523326233272332823329233302333123332233332333423335233362333723338233392334023341233422334323344233452334623347233482334923350233512335223353233542335523356233572335823359233602336123362233632336423365233662336723368233692337023371233722337323374233752337623377233782337923380233812338223383233842338523386233872338823389233902339123392233932339423395233962339723398233992340023401234022340323404234052340623407234082340923410234112341223413234142341523416234172341823419234202342123422234232342423425234262342723428234292343023431234322343323434234352343623437234382343923440234412344223443234442344523446234472344823449234502345123452234532345423455234562345723458234592346023461234622346323464234652346623467234682346923470234712347223473234742347523476234772347823479234802348123482234832348423485234862348723488234892349023491234922349323494234952349623497234982349923500235012350223503235042350523506235072350823509235102351123512235132351423515235162351723518235192352023521235222352323524235252352623527235282352923530235312353223533235342353523536235372353823539235402354123542235432354423545235462354723548235492355023551235522355323554235552355623557235582355923560235612356223563235642356523566235672356823569235702357123572235732357423575235762357723578235792358023581235822358323584235852358623587235882358923590235912359223593235942359523596235972359823599236002360123602236032360423605236062360723608236092361023611236122361323614236152361623617236182361923620236212362223623236242362523626236272362823629236302363123632236332363423635236362363723638236392364023641236422364323644236452364623647236482364923650236512365223653236542365523656236572365823659236602366123662236632366423665236662366723668236692367023671236722367323674236752367623677236782367923680236812368223683236842368523686236872368823689236902369123692236932369423695236962369723698236992370023701237022370323704237052370623707237082370923710237112371223713237142371523716237172371823719237202372123722237232372423725237262372723728237292373023731237322373323734237352373623737237382373923740237412374223743237442374523746237472374823749237502375123752237532375423755237562375723758237592376023761237622376323764237652376623767237682376923770237712377223773237742377523776237772377823779237802378123782237832378423785237862378723788237892379023791237922379323794237952379623797237982379923800238012380223803238042380523806238072380823809238102381123812238132381423815238162381723818238192382023821238222382323824238252382623827238282382923830238312383223833238342383523836238372383823839238402384123842238432384423845238462384723848238492385023851238522385323854238552385623857238582385923860238612386223863238642386523866238672386823869238702387123872238732387423875238762387723878238792388023881238822388323884238852388623887238882388923890238912389223893238942389523896238972389823899239002390123902239032390423905239062390723908239092391023911239122391323914239152391623917239182391923920239212392223923239242392523926239272392823929239302393123932239332393423935239362393723938239392394023941239422394323944239452394623947239482394923950239512395223953239542395523956239572395823959239602396123962239632396423965239662396723968239692397023971239722397323974239752397623977239782397923980239812398223983239842398523986239872398823989239902399123992239932399423995239962399723998239992400024001240022400324004240052400624007240082400924010240112401224013240142401524016240172401824019240202402124022240232402424025240262402724028240292403024031240322403324034240352403624037240382403924040240412404224043240442404524046240472404824049240502405124052240532405424055240562405724058240592406024061240622406324064240652406624067240682406924070240712407224073240742407524076240772407824079240802408124082240832408424085240862408724088240892409024091240922409324094240952409624097240982409924100241012410224103241042410524106241072410824109241102411124112241132411424115241162411724118241192412024121241222412324124241252412624127241282412924130241312413224133241342413524136241372413824139241402414124142241432414424145241462414724148241492415024151241522415324154241552415624157241582415924160241612416224163241642416524166241672416824169241702417124172241732417424175241762417724178241792418024181241822418324184241852418624187241882418924190241912419224193241942419524196241972419824199242002420124202242032420424205242062420724208242092421024211242122421324214242152421624217242182421924220242212422224223242242422524226242272422824229242302423124232242332423424235242362423724238242392424024241242422424324244242452424624247242482424924250242512425224253242542425524256242572425824259242602426124262242632426424265242662426724268242692427024271242722427324274242752427624277242782427924280242812428224283242842428524286242872428824289242902429124292242932429424295242962429724298242992430024301243022430324304243052430624307243082430924310243112431224313243142431524316243172431824319243202432124322243232432424325243262432724328243292433024331243322433324334243352433624337243382433924340243412434224343243442434524346243472434824349243502435124352243532435424355243562435724358243592436024361243622436324364243652436624367243682436924370243712437224373243742437524376243772437824379243802438124382243832438424385243862438724388243892439024391243922439324394243952439624397243982439924400244012440224403244042440524406244072440824409244102441124412244132441424415244162441724418244192442024421244222442324424244252442624427244282442924430244312443224433244342443524436244372443824439244402444124442244432444424445244462444724448244492445024451244522445324454244552445624457244582445924460244612446224463244642446524466244672446824469244702447124472244732447424475244762447724478244792448024481244822448324484244852448624487244882448924490244912449224493244942449524496244972449824499245002450124502245032450424505245062450724508245092451024511245122451324514245152451624517245182451924520245212452224523245242452524526245272452824529245302453124532245332453424535245362453724538245392454024541245422454324544245452454624547245482454924550245512455224553245542455524556245572455824559245602456124562245632456424565245662456724568245692457024571245722457324574245752457624577245782457924580245812458224583245842458524586245872458824589245902459124592245932459424595245962459724598245992460024601246022460324604246052460624607246082460924610246112461224613246142461524616246172461824619246202462124622246232462424625246262462724628246292463024631246322463324634246352463624637246382463924640246412464224643246442464524646246472464824649246502465124652246532465424655246562465724658246592466024661246622466324664246652466624667246682466924670246712467224673246742467524676246772467824679246802468124682246832468424685246862468724688246892469024691246922469324694246952469624697246982469924700247012470224703247042470524706247072470824709247102471124712247132471424715247162471724718247192472024721247222472324724247252472624727247282472924730247312473224733247342473524736247372473824739247402474124742247432474424745247462474724748247492475024751247522475324754247552475624757247582475924760247612476224763247642476524766247672476824769247702477124772247732477424775247762477724778247792478024781247822478324784247852478624787247882478924790247912479224793247942479524796247972479824799248002480124802248032480424805248062480724808248092481024811248122481324814248152481624817248182481924820248212482224823248242482524826248272482824829248302483124832248332483424835248362483724838248392484024841248422484324844248452484624847248482484924850248512485224853248542485524856248572485824859248602486124862248632486424865248662486724868248692487024871248722487324874248752487624877248782487924880248812488224883248842488524886248872488824889248902489124892248932489424895248962489724898248992490024901249022490324904249052490624907249082490924910249112491224913249142491524916249172491824919249202492124922249232492424925249262492724928249292493024931249322493324934249352493624937249382493924940249412494224943249442494524946249472494824949249502495124952249532495424955249562495724958249592496024961249622496324964249652496624967249682496924970249712497224973249742497524976249772497824979249802498124982249832498424985249862498724988249892499024991249922499324994249952499624997249982499925000250012500225003250042500525006250072500825009250102501125012250132501425015250162501725018250192502025021250222502325024250252502625027250282502925030250312503225033250342503525036250372503825039250402504125042250432504425045250462504725048250492505025051250522505325054250552505625057250582505925060250612506225063250642506525066250672506825069250702507125072250732507425075250762507725078250792508025081250822508325084250852508625087250882508925090250912509225093250942509525096250972509825099251002510125102251032510425105251062510725108251092511025111251122511325114251152511625117251182511925120251212512225123251242512525126251272512825129251302513125132251332513425135251362513725138251392514025141251422514325144251452514625147251482514925150251512515225153251542515525156251572515825159251602516125162251632516425165251662516725168251692517025171251722517325174251752517625177251782517925180251812518225183251842518525186251872518825189251902519125192251932519425195251962519725198251992520025201252022520325204252052520625207252082520925210252112521225213252142521525216252172521825219252202522125222252232522425225252262522725228252292523025231252322523325234252352523625237252382523925240252412524225243252442524525246252472524825249252502525125252252532525425255252562525725258252592526025261252622526325264252652526625267252682526925270252712527225273252742527525276252772527825279252802528125282252832528425285252862528725288252892529025291252922529325294252952529625297252982529925300253012530225303253042530525306253072530825309253102531125312253132531425315253162531725318253192532025321253222532325324253252532625327253282532925330253312533225333253342533525336253372533825339253402534125342253432534425345253462534725348253492535025351253522535325354253552535625357253582535925360253612536225363253642536525366253672536825369253702537125372253732537425375253762537725378253792538025381253822538325384253852538625387253882538925390253912539225393253942539525396253972539825399254002540125402254032540425405254062540725408254092541025411254122541325414254152541625417254182541925420254212542225423254242542525426254272542825429254302543125432254332543425435254362543725438254392544025441254422544325444254452544625447254482544925450254512545225453254542545525456254572545825459254602546125462254632546425465254662546725468254692547025471254722547325474254752547625477254782547925480254812548225483254842548525486254872548825489254902549125492254932549425495254962549725498254992550025501255022550325504255052550625507255082550925510255112551225513255142551525516255172551825519255202552125522255232552425525255262552725528255292553025531255322553325534255352553625537255382553925540255412554225543255442554525546255472554825549255502555125552255532555425555255562555725558255592556025561255622556325564255652556625567255682556925570255712557225573255742557525576255772557825579255802558125582255832558425585255862558725588255892559025591255922559325594255952559625597255982559925600256012560225603256042560525606256072560825609256102561125612256132561425615256162561725618256192562025621256222562325624256252562625627256282562925630256312563225633256342563525636256372563825639256402564125642256432564425645256462564725648256492565025651256522565325654256552565625657256582565925660256612566225663256642566525666256672566825669256702567125672256732567425675256762567725678256792568025681256822568325684256852568625687256882568925690256912569225693256942569525696256972569825699257002570125702257032570425705257062570725708257092571025711257122571325714257152571625717257182571925720257212572225723257242572525726257272572825729257302573125732257332573425735257362573725738257392574025741257422574325744257452574625747257482574925750257512575225753257542575525756257572575825759257602576125762257632576425765257662576725768257692577025771257722577325774257752577625777257782577925780257812578225783257842578525786257872578825789257902579125792257932579425795257962579725798257992580025801258022580325804258052580625807258082580925810258112581225813258142581525816258172581825819258202582125822258232582425825258262582725828258292583025831258322583325834258352583625837258382583925840258412584225843258442584525846258472584825849258502585125852258532585425855258562585725858258592586025861258622586325864258652586625867258682586925870258712587225873258742587525876258772587825879258802588125882258832588425885258862588725888258892589025891258922589325894258952589625897258982589925900259012590225903259042590525906259072590825909259102591125912259132591425915259162591725918259192592025921259222592325924259252592625927259282592925930259312593225933259342593525936259372593825939259402594125942259432594425945259462594725948259492595025951259522595325954259552595625957259582595925960259612596225963259642596525966259672596825969259702597125972259732597425975259762597725978259792598025981259822598325984259852598625987259882598925990259912599225993259942599525996259972599825999260002600126002260032600426005260062600726008260092601026011260122601326014260152601626017260182601926020260212602226023260242602526026260272602826029260302603126032260332603426035260362603726038260392604026041260422604326044260452604626047260482604926050260512605226053260542605526056260572605826059260602606126062260632606426065260662606726068260692607026071260722607326074260752607626077260782607926080260812608226083260842608526086260872608826089260902609126092260932609426095260962609726098260992610026101261022610326104261052610626107261082610926110261112611226113261142611526116261172611826119261202612126122261232612426125261262612726128261292613026131261322613326134261352613626137261382613926140261412614226143261442614526146261472614826149261502615126152261532615426155261562615726158261592616026161261622616326164261652616626167261682616926170261712617226173261742617526176261772617826179261802618126182261832618426185261862618726188261892619026191261922619326194261952619626197261982619926200262012620226203262042620526206262072620826209262102621126212262132621426215262162621726218262192622026221262222622326224262252622626227262282622926230262312623226233262342623526236262372623826239262402624126242262432624426245262462624726248262492625026251262522625326254262552625626257262582625926260262612626226263262642626526266262672626826269262702627126272262732627426275262762627726278262792628026281262822628326284262852628626287262882628926290262912629226293262942629526296262972629826299263002630126302263032630426305263062630726308263092631026311263122631326314263152631626317263182631926320263212632226323263242632526326263272632826329263302633126332263332633426335263362633726338263392634026341263422634326344263452634626347263482634926350263512635226353263542635526356263572635826359263602636126362263632636426365263662636726368263692637026371263722637326374263752637626377263782637926380263812638226383263842638526386263872638826389263902639126392263932639426395263962639726398263992640026401264022640326404264052640626407264082640926410264112641226413264142641526416264172641826419264202642126422264232642426425264262642726428264292643026431264322643326434264352643626437264382643926440264412644226443264442644526446264472644826449264502645126452264532645426455264562645726458264592646026461264622646326464264652646626467264682646926470264712647226473264742647526476264772647826479264802648126482264832648426485264862648726488264892649026491264922649326494264952649626497264982649926500265012650226503265042650526506265072650826509265102651126512265132651426515265162651726518265192652026521265222652326524265252652626527265282652926530265312653226533265342653526536265372653826539265402654126542265432654426545265462654726548265492655026551265522655326554265552655626557265582655926560265612656226563265642656526566265672656826569265702657126572265732657426575265762657726578265792658026581265822658326584265852658626587265882658926590265912659226593265942659526596265972659826599266002660126602266032660426605266062660726608266092661026611266122661326614266152661626617266182661926620266212662226623266242662526626266272662826629266302663126632266332663426635266362663726638266392664026641266422664326644266452664626647266482664926650266512665226653266542665526656266572665826659266602666126662266632666426665266662666726668266692667026671266722667326674266752667626677266782667926680266812668226683266842668526686266872668826689266902669126692266932669426695266962669726698266992670026701267022670326704267052670626707267082670926710267112671226713267142671526716267172671826719267202672126722267232672426725267262672726728267292673026731267322673326734267352673626737267382673926740267412674226743267442674526746267472674826749267502675126752267532675426755267562675726758267592676026761267622676326764267652676626767267682676926770267712677226773267742677526776267772677826779267802678126782267832678426785267862678726788267892679026791267922679326794267952679626797267982679926800268012680226803268042680526806268072680826809268102681126812268132681426815268162681726818268192682026821268222682326824268252682626827268282682926830268312683226833268342683526836268372683826839268402684126842268432684426845268462684726848268492685026851268522685326854268552685626857268582685926860268612686226863268642686526866268672686826869268702687126872268732687426875268762687726878268792688026881268822688326884268852688626887268882688926890268912689226893268942689526896268972689826899269002690126902269032690426905269062690726908269092691026911269122691326914269152691626917269182691926920269212692226923269242692526926269272692826929269302693126932269332693426935269362693726938269392694026941269422694326944269452694626947269482694926950269512695226953269542695526956269572695826959269602696126962269632696426965269662696726968269692697026971269722697326974269752697626977269782697926980269812698226983269842698526986269872698826989269902699126992269932699426995269962699726998269992700027001270022700327004270052700627007270082700927010270112701227013270142701527016270172701827019270202702127022270232702427025270262702727028270292703027031270322703327034270352703627037270382703927040270412704227043270442704527046270472704827049270502705127052270532705427055270562705727058270592706027061270622706327064270652706627067270682706927070270712707227073270742707527076270772707827079270802708127082270832708427085270862708727088270892709027091270922709327094270952709627097270982709927100271012710227103271042710527106271072710827109271102711127112271132711427115271162711727118271192712027121271222712327124271252712627127271282712927130271312713227133271342713527136271372713827139271402714127142271432714427145271462714727148271492715027151271522715327154271552715627157271582715927160271612716227163271642716527166271672716827169271702717127172271732717427175271762717727178271792718027181271822718327184271852718627187271882718927190271912719227193271942719527196271972719827199272002720127202272032720427205272062720727208272092721027211272122721327214272152721627217272182721927220272212722227223272242722527226272272722827229272302723127232272332723427235272362723727238272392724027241272422724327244272452724627247272482724927250272512725227253272542725527256272572725827259272602726127262272632726427265272662726727268272692727027271272722727327274272752727627277272782727927280272812728227283272842728527286272872728827289272902729127292272932729427295272962729727298272992730027301273022730327304273052730627307273082730927310273112731227313273142731527316273172731827319273202732127322273232732427325273262732727328273292733027331273322733327334273352733627337273382733927340273412734227343273442734527346273472734827349273502735127352273532735427355273562735727358273592736027361273622736327364273652736627367273682736927370273712737227373273742737527376273772737827379273802738127382273832738427385273862738727388273892739027391273922739327394273952739627397273982739927400274012740227403274042740527406274072740827409274102741127412274132741427415274162741727418274192742027421274222742327424274252742627427274282742927430274312743227433274342743527436274372743827439274402744127442274432744427445274462744727448274492745027451274522745327454274552745627457274582745927460274612746227463274642746527466274672746827469274702747127472274732747427475274762747727478274792748027481274822748327484274852748627487274882748927490274912749227493274942749527496274972749827499275002750127502275032750427505275062750727508275092751027511275122751327514275152751627517275182751927520275212752227523275242752527526275272752827529275302753127532275332753427535275362753727538275392754027541275422754327544275452754627547275482754927550275512755227553275542755527556275572755827559275602756127562275632756427565275662756727568275692757027571275722757327574275752757627577275782757927580275812758227583275842758527586275872758827589275902759127592275932759427595275962759727598275992760027601276022760327604276052760627607276082760927610276112761227613276142761527616276172761827619276202762127622276232762427625276262762727628276292763027631276322763327634276352763627637276382763927640276412764227643276442764527646276472764827649276502765127652276532765427655276562765727658276592766027661276622766327664276652766627667276682766927670276712767227673276742767527676276772767827679276802768127682276832768427685276862768727688276892769027691276922769327694276952769627697276982769927700277012770227703277042770527706277072770827709277102771127712277132771427715277162771727718277192772027721277222772327724277252772627727277282772927730277312773227733277342773527736277372773827739277402774127742277432774427745277462774727748277492775027751277522775327754277552775627757277582775927760277612776227763277642776527766277672776827769277702777127772277732777427775277762777727778277792778027781277822778327784277852778627787277882778927790277912779227793277942779527796277972779827799278002780127802278032780427805278062780727808278092781027811278122781327814278152781627817278182781927820278212782227823278242782527826278272782827829278302783127832278332783427835278362783727838278392784027841278422784327844278452784627847278482784927850278512785227853278542785527856278572785827859278602786127862278632786427865278662786727868278692787027871278722787327874278752787627877278782787927880278812788227883278842788527886278872788827889278902789127892278932789427895278962789727898278992790027901279022790327904279052790627907279082790927910279112791227913279142791527916279172791827919279202792127922279232792427925279262792727928279292793027931279322793327934279352793627937279382793927940279412794227943279442794527946279472794827949279502795127952279532795427955279562795727958279592796027961279622796327964279652796627967279682796927970279712797227973279742797527976279772797827979279802798127982279832798427985279862798727988279892799027991279922799327994279952799627997279982799928000280012800228003280042800528006280072800828009280102801128012280132801428015280162801728018280192802028021280222802328024280252802628027280282802928030280312803228033280342803528036280372803828039280402804128042280432804428045280462804728048280492805028051280522805328054280552805628057280582805928060280612806228063280642806528066280672806828069280702807128072280732807428075280762807728078280792808028081280822808328084280852808628087280882808928090280912809228093280942809528096280972809828099281002810128102281032810428105281062810728108281092811028111281122811328114281152811628117281182811928120281212812228123281242812528126281272812828129281302813128132281332813428135281362813728138281392814028141281422814328144281452814628147281482814928150281512815228153281542815528156281572815828159281602816128162281632816428165281662816728168281692817028171281722817328174281752817628177281782817928180281812818228183281842818528186281872818828189281902819128192281932819428195281962819728198281992820028201282022820328204282052820628207282082820928210282112821228213282142821528216282172821828219282202822128222282232822428225282262822728228282292823028231282322823328234282352823628237282382823928240282412824228243282442824528246282472824828249282502825128252282532825428255282562825728258282592826028261282622826328264282652826628267282682826928270282712827228273282742827528276282772827828279282802828128282282832828428285282862828728288282892829028291282922829328294282952829628297282982829928300283012830228303283042830528306283072830828309283102831128312283132831428315283162831728318283192832028321283222832328324283252832628327283282832928330283312833228333283342833528336283372833828339283402834128342283432834428345283462834728348283492835028351283522835328354283552835628357283582835928360283612836228363283642836528366283672836828369283702837128372283732837428375283762837728378283792838028381283822838328384283852838628387283882838928390283912839228393283942839528396283972839828399284002840128402284032840428405284062840728408284092841028411284122841328414284152841628417284182841928420284212842228423284242842528426284272842828429284302843128432284332843428435284362843728438284392844028441284422844328444284452844628447284482844928450284512845228453284542845528456284572845828459284602846128462284632846428465284662846728468284692847028471284722847328474284752847628477284782847928480284812848228483284842848528486284872848828489284902849128492284932849428495284962849728498284992850028501285022850328504285052850628507285082850928510285112851228513285142851528516285172851828519285202852128522285232852428525285262852728528285292853028531285322853328534285352853628537285382853928540285412854228543285442854528546285472854828549285502855128552285532855428555285562855728558285592856028561285622856328564285652856628567285682856928570285712857228573285742857528576285772857828579285802858128582285832858428585285862858728588285892859028591285922859328594285952859628597285982859928600286012860228603286042860528606286072860828609286102861128612286132861428615286162861728618286192862028621286222862328624286252862628627286282862928630286312863228633286342863528636286372863828639286402864128642286432864428645286462864728648286492865028651286522865328654286552865628657286582865928660286612866228663286642866528666286672866828669286702867128672286732867428675286762867728678286792868028681286822868328684286852868628687286882868928690286912869228693286942869528696286972869828699287002870128702287032870428705287062870728708287092871028711287122871328714287152871628717287182871928720287212872228723287242872528726287272872828729287302873128732287332873428735287362873728738287392874028741287422874328744287452874628747287482874928750287512875228753287542875528756287572875828759287602876128762287632876428765287662876728768287692877028771287722877328774287752877628777287782877928780287812878228783287842878528786287872878828789287902879128792287932879428795287962879728798287992880028801288022880328804288052880628807288082880928810288112881228813288142881528816288172881828819288202882128822288232882428825288262882728828288292883028831288322883328834288352883628837288382883928840288412884228843288442884528846288472884828849288502885128852288532885428855288562885728858288592886028861288622886328864288652886628867288682886928870288712887228873288742887528876288772887828879288802888128882288832888428885288862888728888288892889028891288922889328894288952889628897288982889928900289012890228903289042890528906289072890828909289102891128912289132891428915289162891728918289192892028921289222892328924289252892628927289282892928930289312893228933289342893528936289372893828939289402894128942289432894428945289462894728948289492895028951289522895328954289552895628957289582895928960289612896228963289642896528966289672896828969289702897128972289732897428975289762897728978289792898028981289822898328984289852898628987289882898928990289912899228993289942899528996289972899828999290002900129002290032900429005290062900729008290092901029011290122901329014290152901629017290182901929020290212902229023290242902529026290272902829029290302903129032290332903429035290362903729038290392904029041290422904329044290452904629047290482904929050290512905229053290542905529056290572905829059290602906129062290632906429065290662906729068290692907029071290722907329074290752907629077290782907929080290812908229083290842908529086290872908829089290902909129092290932909429095290962909729098290992910029101291022910329104291052910629107291082910929110291112911229113291142911529116291172911829119291202912129122291232912429125291262912729128291292913029131291322913329134291352913629137291382913929140291412914229143291442914529146291472914829149291502915129152291532915429155291562915729158291592916029161291622916329164291652916629167291682916929170291712917229173291742917529176291772917829179291802918129182291832918429185291862918729188291892919029191291922919329194291952919629197291982919929200292012920229203292042920529206292072920829209292102921129212292132921429215292162921729218292192922029221292222922329224292252922629227292282922929230292312923229233292342923529236292372923829239292402924129242292432924429245292462924729248292492925029251292522925329254292552925629257292582925929260292612926229263292642926529266292672926829269292702927129272292732927429275292762927729278292792928029281292822928329284292852928629287292882928929290292912929229293292942929529296292972929829299293002930129302293032930429305293062930729308293092931029311293122931329314293152931629317293182931929320293212932229323293242932529326293272932829329293302933129332293332933429335293362933729338293392934029341293422934329344293452934629347293482934929350293512935229353293542935529356293572935829359293602936129362293632936429365293662936729368293692937029371293722937329374293752937629377293782937929380293812938229383293842938529386293872938829389293902939129392293932939429395293962939729398293992940029401294022940329404294052940629407294082940929410294112941229413294142941529416294172941829419294202942129422294232942429425294262942729428294292943029431294322943329434294352943629437294382943929440294412944229443294442944529446294472944829449294502945129452294532945429455294562945729458294592946029461294622946329464294652946629467294682946929470294712947229473294742947529476294772947829479294802948129482294832948429485294862948729488294892949029491294922949329494294952949629497294982949929500295012950229503295042950529506295072950829509295102951129512295132951429515295162951729518295192952029521295222952329524295252952629527295282952929530295312953229533295342953529536295372953829539295402954129542295432954429545295462954729548295492955029551295522955329554295552955629557295582955929560295612956229563295642956529566295672956829569295702957129572295732957429575295762957729578295792958029581295822958329584295852958629587295882958929590295912959229593295942959529596295972959829599296002960129602296032960429605296062960729608296092961029611296122961329614296152961629617296182961929620296212962229623296242962529626296272962829629296302963129632296332963429635296362963729638296392964029641296422964329644296452964629647296482964929650296512965229653296542965529656296572965829659296602966129662296632966429665296662966729668296692967029671296722967329674296752967629677296782967929680296812968229683296842968529686296872968829689296902969129692296932969429695296962969729698296992970029701297022970329704297052970629707297082970929710297112971229713297142971529716297172971829719297202972129722297232972429725297262972729728297292973029731297322973329734297352973629737297382973929740297412974229743297442974529746297472974829749297502975129752297532975429755297562975729758297592976029761297622976329764297652976629767297682976929770297712977229773297742977529776297772977829779297802978129782297832978429785297862978729788297892979029791297922979329794297952979629797297982979929800298012980229803298042980529806298072980829809298102981129812298132981429815298162981729818298192982029821298222982329824298252982629827298282982929830298312983229833298342983529836298372983829839298402984129842298432984429845298462984729848298492985029851298522985329854298552985629857298582985929860298612986229863298642986529866298672986829869298702987129872298732987429875298762987729878298792988029881298822988329884298852988629887298882988929890298912989229893298942989529896298972989829899299002990129902299032990429905299062990729908299092991029911299122991329914299152991629917299182991929920299212992229923299242992529926299272992829929299302993129932299332993429935299362993729938299392994029941299422994329944299452994629947299482994929950299512995229953299542995529956299572995829959299602996129962299632996429965299662996729968299692997029971299722997329974299752997629977299782997929980299812998229983299842998529986299872998829989299902999129992299932999429995299962999729998299993000030001300023000330004300053000630007300083000930010300113001230013300143001530016300173001830019300203002130022300233002430025300263002730028300293003030031300323003330034300353003630037300383003930040300413004230043300443004530046300473004830049300503005130052300533005430055300563005730058300593006030061300623006330064300653006630067300683006930070300713007230073300743007530076300773007830079300803008130082300833008430085300863008730088300893009030091300923009330094300953009630097300983009930100301013010230103301043010530106301073010830109301103011130112301133011430115301163011730118301193012030121301223012330124301253012630127301283012930130301313013230133301343013530136301373013830139301403014130142301433014430145301463014730148301493015030151301523015330154301553015630157301583015930160301613016230163301643016530166301673016830169301703017130172301733017430175301763017730178301793018030181301823018330184301853018630187301883018930190301913019230193301943019530196301973019830199302003020130202302033020430205302063020730208302093021030211302123021330214302153021630217302183021930220302213022230223302243022530226302273022830229302303023130232302333023430235302363023730238302393024030241302423024330244302453024630247302483024930250302513025230253302543025530256302573025830259302603026130262302633026430265302663026730268302693027030271302723027330274302753027630277302783027930280302813028230283302843028530286302873028830289302903029130292302933029430295302963029730298302993030030301303023030330304303053030630307303083030930310303113031230313303143031530316303173031830319303203032130322303233032430325303263032730328303293033030331303323033330334303353033630337303383033930340303413034230343303443034530346303473034830349303503035130352303533035430355303563035730358303593036030361303623036330364303653036630367303683036930370303713037230373303743037530376303773037830379303803038130382303833038430385303863038730388303893039030391303923039330394303953039630397303983039930400304013040230403304043040530406304073040830409304103041130412304133041430415304163041730418304193042030421304223042330424304253042630427304283042930430304313043230433304343043530436304373043830439304403044130442304433044430445304463044730448304493045030451304523045330454304553045630457304583045930460304613046230463304643046530466304673046830469304703047130472304733047430475304763047730478304793048030481304823048330484304853048630487304883048930490304913049230493304943049530496304973049830499305003050130502305033050430505305063050730508305093051030511305123051330514305153051630517305183051930520305213052230523305243052530526305273052830529305303053130532305333053430535305363053730538305393054030541305423054330544305453054630547305483054930550305513055230553305543055530556305573055830559305603056130562305633056430565305663056730568305693057030571305723057330574305753057630577305783057930580305813058230583305843058530586305873058830589305903059130592305933059430595305963059730598305993060030601306023060330604306053060630607306083060930610306113061230613306143061530616306173061830619306203062130622306233062430625306263062730628306293063030631306323063330634306353063630637306383063930640306413064230643306443064530646306473064830649306503065130652306533065430655306563065730658306593066030661306623066330664306653066630667306683066930670306713067230673306743067530676306773067830679306803068130682306833068430685306863068730688306893069030691306923069330694306953069630697306983069930700307013070230703307043070530706307073070830709307103071130712307133071430715307163071730718307193072030721307223072330724307253072630727307283072930730307313073230733307343073530736307373073830739307403074130742307433074430745307463074730748307493075030751307523075330754307553075630757307583075930760307613076230763307643076530766307673076830769307703077130772307733077430775307763077730778307793078030781307823078330784307853078630787307883078930790307913079230793307943079530796307973079830799308003080130802308033080430805308063080730808308093081030811308123081330814308153081630817308183081930820308213082230823308243082530826308273082830829308303083130832308333083430835308363083730838308393084030841308423084330844308453084630847308483084930850308513085230853308543085530856308573085830859308603086130862308633086430865308663086730868308693087030871308723087330874308753087630877308783087930880308813088230883308843088530886308873088830889308903089130892308933089430895308963089730898308993090030901309023090330904309053090630907309083090930910309113091230913309143091530916309173091830919309203092130922309233092430925309263092730928309293093030931309323093330934309353093630937309383093930940309413094230943309443094530946309473094830949309503095130952309533095430955309563095730958309593096030961309623096330964309653096630967309683096930970309713097230973309743097530976309773097830979309803098130982309833098430985309863098730988309893099030991309923099330994309953099630997309983099931000310013100231003310043100531006310073100831009310103101131012310133101431015310163101731018310193102031021310223102331024310253102631027310283102931030310313103231033310343103531036310373103831039310403104131042310433104431045310463104731048310493105031051310523105331054310553105631057310583105931060310613106231063310643106531066310673106831069310703107131072310733107431075310763107731078310793108031081310823108331084310853108631087310883108931090310913109231093310943109531096310973109831099311003110131102311033110431105311063110731108311093111031111311123111331114311153111631117311183111931120311213112231123311243112531126311273112831129311303113131132311333113431135311363113731138311393114031141311423114331144311453114631147311483114931150311513115231153311543115531156311573115831159311603116131162311633116431165311663116731168311693117031171311723117331174311753117631177311783117931180311813118231183311843118531186311873118831189311903119131192311933119431195311963119731198311993120031201312023120331204312053120631207312083120931210312113121231213312143121531216312173121831219312203122131222312233122431225312263122731228312293123031231312323123331234312353123631237312383123931240312413124231243312443124531246312473124831249312503125131252312533125431255312563125731258312593126031261312623126331264312653126631267312683126931270312713127231273312743127531276312773127831279312803128131282312833128431285312863128731288312893129031291312923129331294312953129631297312983129931300313013130231303313043130531306313073130831309313103131131312313133131431315313163131731318313193132031321313223132331324313253132631327313283132931330313313133231333313343133531336313373133831339313403134131342313433134431345313463134731348313493135031351313523135331354313553135631357313583135931360313613136231363313643136531366313673136831369313703137131372313733137431375313763137731378313793138031381313823138331384313853138631387313883138931390313913139231393313943139531396313973139831399314003140131402314033140431405314063140731408314093141031411314123141331414314153141631417314183141931420314213142231423314243142531426314273142831429314303143131432314333143431435314363143731438314393144031441314423144331444314453144631447314483144931450314513145231453314543145531456314573145831459314603146131462314633146431465314663146731468314693147031471314723147331474314753147631477314783147931480314813148231483314843148531486314873148831489314903149131492314933149431495314963149731498314993150031501315023150331504315053150631507315083150931510315113151231513315143151531516315173151831519315203152131522315233152431525315263152731528315293153031531315323153331534315353153631537315383153931540315413154231543315443154531546315473154831549315503155131552315533155431555315563155731558315593156031561315623156331564315653156631567315683156931570315713157231573315743157531576315773157831579315803158131582315833158431585315863158731588315893159031591315923159331594315953159631597315983159931600316013160231603316043160531606316073160831609316103161131612316133161431615316163161731618316193162031621316223162331624316253162631627316283162931630316313163231633316343163531636316373163831639316403164131642316433164431645316463164731648316493165031651316523165331654316553165631657316583165931660316613166231663316643166531666316673166831669316703167131672316733167431675316763167731678316793168031681316823168331684316853168631687316883168931690316913169231693316943169531696316973169831699317003170131702317033170431705317063170731708317093171031711317123171331714317153171631717317183171931720317213172231723317243172531726317273172831729317303173131732317333173431735317363173731738317393174031741317423174331744317453174631747317483174931750317513175231753317543175531756317573175831759317603176131762317633176431765317663176731768317693177031771317723177331774317753177631777317783177931780317813178231783317843178531786317873178831789317903179131792317933179431795317963179731798317993180031801318023180331804318053180631807318083180931810318113181231813318143181531816318173181831819318203182131822318233182431825318263182731828318293183031831318323183331834318353183631837318383183931840318413184231843318443184531846318473184831849318503185131852318533185431855318563185731858318593186031861318623186331864318653186631867318683186931870318713187231873318743187531876318773187831879318803188131882318833188431885318863188731888318893189031891318923189331894318953189631897318983189931900319013190231903319043190531906319073190831909319103191131912319133191431915319163191731918319193192031921319223192331924319253192631927319283192931930319313193231933319343193531936319373193831939319403194131942319433194431945319463194731948319493195031951319523195331954319553195631957319583195931960319613196231963319643196531966319673196831969319703197131972319733197431975319763197731978319793198031981319823198331984319853198631987319883198931990319913199231993319943199531996319973199831999320003200132002320033200432005320063200732008320093201032011320123201332014320153201632017320183201932020320213202232023320243202532026320273202832029320303203132032320333203432035320363203732038320393204032041320423204332044320453204632047320483204932050320513205232053320543205532056320573205832059320603206132062320633206432065320663206732068320693207032071320723207332074320753207632077320783207932080320813208232083320843208532086320873208832089320903209132092320933209432095320963209732098320993210032101321023210332104321053210632107321083210932110321113211232113321143211532116321173211832119321203212132122321233212432125321263212732128321293213032131321323213332134321353213632137321383213932140321413214232143321443214532146321473214832149321503215132152321533215432155321563215732158321593216032161321623216332164321653216632167321683216932170321713217232173321743217532176321773217832179321803218132182321833218432185321863218732188321893219032191321923219332194321953219632197321983219932200322013220232203322043220532206322073220832209322103221132212322133221432215322163221732218322193222032221322223222332224322253222632227322283222932230322313223232233322343223532236322373223832239322403224132242322433224432245322463224732248322493225032251322523225332254322553225632257322583225932260322613226232263322643226532266322673226832269322703227132272322733227432275322763227732278322793228032281322823228332284322853228632287322883228932290322913229232293322943229532296322973229832299323003230132302323033230432305323063230732308323093231032311323123231332314323153231632317323183231932320323213232232323323243232532326323273232832329323303233132332323333233432335323363233732338323393234032341323423234332344323453234632347323483234932350323513235232353323543235532356323573235832359323603236132362323633236432365323663236732368323693237032371323723237332374323753237632377323783237932380323813238232383323843238532386323873238832389323903239132392323933239432395323963239732398323993240032401324023240332404324053240632407324083240932410324113241232413324143241532416324173241832419324203242132422324233242432425324263242732428324293243032431324323243332434324353243632437324383243932440324413244232443324443244532446324473244832449324503245132452324533245432455324563245732458324593246032461324623246332464324653246632467324683246932470324713247232473324743247532476324773247832479324803248132482324833248432485324863248732488324893249032491324923249332494324953249632497324983249932500325013250232503325043250532506325073250832509325103251132512325133251432515325163251732518325193252032521325223252332524325253252632527325283252932530325313253232533325343253532536325373253832539325403254132542325433254432545325463254732548325493255032551325523255332554325553255632557325583255932560325613256232563325643256532566325673256832569325703257132572325733257432575325763257732578325793258032581325823258332584325853258632587325883258932590325913259232593325943259532596325973259832599326003260132602326033260432605326063260732608326093261032611326123261332614326153261632617326183261932620326213262232623326243262532626326273262832629326303263132632326333263432635326363263732638326393264032641326423264332644326453264632647326483264932650326513265232653326543265532656326573265832659326603266132662326633266432665326663266732668326693267032671326723267332674326753267632677326783267932680326813268232683326843268532686326873268832689326903269132692326933269432695326963269732698326993270032701327023270332704327053270632707327083270932710327113271232713327143271532716327173271832719327203272132722327233272432725327263272732728327293273032731327323273332734327353273632737327383273932740327413274232743327443274532746327473274832749327503275132752327533275432755327563275732758327593276032761327623276332764327653276632767327683276932770327713277232773327743277532776327773277832779327803278132782327833278432785327863278732788327893279032791327923279332794327953279632797327983279932800328013280232803328043280532806328073280832809328103281132812328133281432815328163281732818328193282032821328223282332824328253282632827328283282932830328313283232833328343283532836328373283832839328403284132842328433284432845328463284732848328493285032851328523285332854328553285632857328583285932860328613286232863328643286532866328673286832869328703287132872328733287432875328763287732878328793288032881328823288332884328853288632887328883288932890328913289232893328943289532896328973289832899329003290132902329033290432905329063290732908329093291032911329123291332914329153291632917329183291932920329213292232923329243292532926329273292832929329303293132932329333293432935329363293732938329393294032941329423294332944329453294632947329483294932950329513295232953329543295532956329573295832959329603296132962329633296432965329663296732968329693297032971329723297332974329753297632977329783297932980329813298232983329843298532986329873298832989329903299132992329933299432995329963299732998329993300033001330023300333004330053300633007330083300933010330113301233013330143301533016330173301833019330203302133022330233302433025330263302733028330293303033031330323303333034330353303633037330383303933040330413304233043330443304533046330473304833049330503305133052330533305433055330563305733058330593306033061330623306333064330653306633067330683306933070330713307233073330743307533076330773307833079330803308133082330833308433085330863308733088330893309033091330923309333094330953309633097330983309933100331013310233103331043310533106331073310833109331103311133112331133311433115331163311733118331193312033121331223312333124331253312633127331283312933130331313313233133331343313533136331373313833139331403314133142331433314433145331463314733148331493315033151331523315333154331553315633157331583315933160331613316233163331643316533166331673316833169331703317133172331733317433175331763317733178331793318033181331823318333184331853318633187331883318933190331913319233193331943319533196331973319833199332003320133202332033320433205332063320733208332093321033211332123321333214332153321633217332183321933220332213322233223332243322533226332273322833229332303323133232332333323433235332363323733238332393324033241332423324333244332453324633247332483324933250332513325233253332543325533256332573325833259332603326133262332633326433265332663326733268332693327033271332723327333274332753327633277332783327933280332813328233283332843328533286332873328833289332903329133292332933329433295332963329733298332993330033301333023330333304333053330633307333083330933310333113331233313333143331533316333173331833319333203332133322333233332433325333263332733328333293333033331333323333333334333353333633337333383333933340333413334233343333443334533346333473334833349333503335133352333533335433355333563335733358333593336033361333623336333364333653336633367333683336933370333713337233373333743337533376333773337833379333803338133382333833338433385333863338733388333893339033391333923339333394333953339633397333983339933400334013340233403334043340533406334073340833409334103341133412334133341433415334163341733418334193342033421334223342333424334253342633427334283342933430334313343233433334343343533436334373343833439334403344133442334433344433445334463344733448334493345033451334523345333454334553345633457334583345933460334613346233463334643346533466334673346833469334703347133472334733347433475334763347733478334793348033481334823348333484334853348633487334883348933490334913349233493334943349533496334973349833499335003350133502335033350433505335063350733508335093351033511335123351333514335153351633517335183351933520335213352233523335243352533526335273352833529335303353133532335333353433535335363353733538335393354033541335423354333544335453354633547335483354933550335513355233553335543355533556335573355833559335603356133562335633356433565335663356733568335693357033571335723357333574335753357633577335783357933580335813358233583335843358533586335873358833589335903359133592335933359433595335963359733598335993360033601336023360333604336053360633607336083360933610336113361233613336143361533616336173361833619336203362133622336233362433625336263362733628336293363033631336323363333634336353363633637336383363933640336413364233643336443364533646336473364833649336503365133652336533365433655336563365733658336593366033661336623366333664336653366633667336683366933670336713367233673336743367533676336773367833679336803368133682336833368433685336863368733688336893369033691336923369333694336953369633697336983369933700337013370233703337043370533706337073370833709337103371133712337133371433715337163371733718337193372033721337223372333724337253372633727337283372933730337313373233733337343373533736337373373833739337403374133742337433374433745337463374733748337493375033751337523375333754337553375633757337583375933760337613376233763337643376533766337673376833769337703377133772337733377433775337763377733778337793378033781337823378333784337853378633787337883378933790337913379233793337943379533796337973379833799338003380133802338033380433805338063380733808338093381033811338123381333814338153381633817338183381933820338213382233823338243382533826338273382833829338303383133832338333383433835338363383733838338393384033841338423384333844338453384633847338483384933850338513385233853338543385533856338573385833859338603386133862338633386433865338663386733868338693387033871338723387333874338753387633877338783387933880338813388233883338843388533886338873388833889338903389133892338933389433895338963389733898338993390033901339023390333904339053390633907339083390933910339113391233913339143391533916339173391833919339203392133922339233392433925339263392733928339293393033931339323393333934339353393633937339383393933940339413394233943339443394533946339473394833949339503395133952339533395433955339563395733958339593396033961339623396333964339653396633967339683396933970339713397233973339743397533976339773397833979339803398133982339833398433985339863398733988339893399033991339923399333994339953399633997339983399934000340013400234003340043400534006340073400834009340103401134012340133401434015340163401734018340193402034021340223402334024340253402634027340283402934030340313403234033340343403534036340373403834039340403404134042340433404434045340463404734048340493405034051340523405334054340553405634057340583405934060340613406234063340643406534066340673406834069340703407134072340733407434075340763407734078340793408034081340823408334084340853408634087340883408934090340913409234093340943409534096340973409834099341003410134102341033410434105341063410734108341093411034111341123411334114341153411634117341183411934120341213412234123341243412534126341273412834129341303413134132341333413434135341363413734138341393414034141341423414334144341453414634147341483414934150341513415234153341543415534156341573415834159341603416134162341633416434165341663416734168341693417034171341723417334174341753417634177341783417934180341813418234183341843418534186341873418834189341903419134192341933419434195341963419734198341993420034201342023420334204342053420634207342083420934210342113421234213342143421534216342173421834219342203422134222342233422434225342263422734228342293423034231342323423334234342353423634237342383423934240342413424234243342443424534246342473424834249342503425134252342533425434255342563425734258342593426034261342623426334264342653426634267342683426934270342713427234273342743427534276342773427834279342803428134282342833428434285342863428734288342893429034291342923429334294342953429634297342983429934300343013430234303343043430534306343073430834309343103431134312343133431434315343163431734318343193432034321343223432334324343253432634327343283432934330343313433234333343343433534336343373433834339343403434134342343433434434345343463434734348343493435034351343523435334354343553435634357343583435934360343613436234363343643436534366343673436834369343703437134372343733437434375343763437734378343793438034381343823438334384343853438634387343883438934390343913439234393343943439534396343973439834399344003440134402344033440434405344063440734408344093441034411344123441334414344153441634417344183441934420344213442234423344243442534426344273442834429344303443134432344333443434435344363443734438344393444034441344423444334444344453444634447344483444934450344513445234453344543445534456344573445834459344603446134462344633446434465344663446734468344693447034471344723447334474344753447634477344783447934480344813448234483344843448534486344873448834489344903449134492344933449434495344963449734498344993450034501345023450334504345053450634507345083450934510345113451234513345143451534516345173451834519345203452134522345233452434525345263452734528345293453034531345323453334534345353453634537345383453934540345413454234543345443454534546345473454834549345503455134552345533455434555345563455734558345593456034561345623456334564345653456634567345683456934570345713457234573345743457534576345773457834579345803458134582345833458434585345863458734588345893459034591345923459334594345953459634597345983459934600346013460234603346043460534606346073460834609346103461134612346133461434615346163461734618346193462034621346223462334624346253462634627346283462934630346313463234633346343463534636346373463834639346403464134642346433464434645346463464734648346493465034651346523465334654346553465634657346583465934660346613466234663346643466534666346673466834669346703467134672346733467434675346763467734678346793468034681346823468334684346853468634687346883468934690346913469234693346943469534696346973469834699347003470134702347033470434705347063470734708347093471034711347123471334714347153471634717347183471934720347213472234723347243472534726347273472834729347303473134732347333473434735347363473734738347393474034741347423474334744347453474634747347483474934750347513475234753347543475534756347573475834759347603476134762347633476434765347663476734768347693477034771347723477334774347753477634777347783477934780347813478234783347843478534786347873478834789347903479134792347933479434795347963479734798347993480034801348023480334804348053480634807348083480934810348113481234813348143481534816348173481834819348203482134822348233482434825348263482734828348293483034831348323483334834348353483634837348383483934840348413484234843348443484534846348473484834849348503485134852348533485434855348563485734858348593486034861348623486334864348653486634867348683486934870348713487234873348743487534876348773487834879348803488134882348833488434885348863488734888348893489034891348923489334894348953489634897348983489934900349013490234903349043490534906349073490834909349103491134912349133491434915349163491734918349193492034921349223492334924349253492634927349283492934930349313493234933349343493534936349373493834939349403494134942349433494434945349463494734948349493495034951349523495334954349553495634957349583495934960349613496234963349643496534966349673496834969349703497134972349733497434975349763497734978349793498034981349823498334984349853498634987349883498934990349913499234993349943499534996349973499834999350003500135002350033500435005350063500735008350093501035011350123501335014350153501635017350183501935020350213502235023350243502535026350273502835029350303503135032350333503435035350363503735038350393504035041350423504335044350453504635047350483504935050350513505235053350543505535056350573505835059350603506135062350633506435065350663506735068350693507035071350723507335074350753507635077350783507935080350813508235083350843508535086350873508835089350903509135092350933509435095350963509735098350993510035101351023510335104351053510635107351083510935110351113511235113351143511535116351173511835119351203512135122351233512435125351263512735128351293513035131351323513335134351353513635137351383513935140351413514235143351443514535146351473514835149351503515135152351533515435155351563515735158351593516035161351623516335164351653516635167351683516935170351713517235173351743517535176351773517835179351803518135182
  1. // 4.0.23 (2014-04-24)
  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. for (var i = 0; i < ids.length; i++) {
  51. var target = exports;
  52. var id = ids[i];
  53. var fragments = id.split(/[.\/]/);
  54. for (var fi = 0; fi < fragments.length - 1; ++fi) {
  55. if (target[fragments[fi]] === undefined) {
  56. target[fragments[fi]] = {};
  57. }
  58. target = target[fragments[fi]];
  59. }
  60. target[fragments[fragments.length - 1]] = modules[id];
  61. }
  62. }
  63. // Included from: js/tinymce/classes/dom/EventUtils.js
  64. /**
  65. * EventUtils.js
  66. *
  67. * Copyright, Moxiecode Systems AB
  68. * Released under LGPL License.
  69. *
  70. * License: http://www.tinymce.com/license
  71. * Contributing: http://www.tinymce.com/contributing
  72. */
  73. /*jshint loopfunc:true*/
  74. /*eslint no-loop-func:0 */
  75. define("tinymce/dom/EventUtils", [], function() {
  76. "use strict";
  77. var eventExpandoPrefix = "mce-data-";
  78. var mouseEventRe = /^(?:mouse|contextmenu)|click/;
  79. var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1};
  80. /**
  81. * Binds a native event to a callback on the speified target.
  82. */
  83. function addEvent(target, name, callback, capture) {
  84. if (target.addEventListener) {
  85. target.addEventListener(name, callback, capture || false);
  86. } else if (target.attachEvent) {
  87. target.attachEvent('on' + name, callback);
  88. }
  89. }
  90. /**
  91. * Unbinds a native event callback on the specified target.
  92. */
  93. function removeEvent(target, name, callback, capture) {
  94. if (target.removeEventListener) {
  95. target.removeEventListener(name, callback, capture || false);
  96. } else if (target.detachEvent) {
  97. target.detachEvent('on' + name, callback);
  98. }
  99. }
  100. /**
  101. * Normalizes a native event object or just adds the event specific methods on a custom event.
  102. */
  103. function fix(originalEvent, data) {
  104. var name, event = data || {}, undef;
  105. // Dummy function that gets replaced on the delegation state functions
  106. function returnFalse() {
  107. return false;
  108. }
  109. // Dummy function that gets replaced on the delegation state functions
  110. function returnTrue() {
  111. return true;
  112. }
  113. // Copy all properties from the original event
  114. for (name in originalEvent) {
  115. // layerX/layerY is deprecated in Chrome and produces a warning
  116. if (!deprecated[name]) {
  117. event[name] = originalEvent[name];
  118. }
  119. }
  120. // Normalize target IE uses srcElement
  121. if (!event.target) {
  122. event.target = event.srcElement || document;
  123. }
  124. // Calculate pageX/Y if missing and clientX/Y available
  125. if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
  126. var eventDoc = event.target.ownerDocument || document;
  127. var doc = eventDoc.documentElement;
  128. var body = eventDoc.body;
  129. event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
  130. ( doc && doc.clientLeft || body && body.clientLeft || 0);
  131. event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0 ) -
  132. ( doc && doc.clientTop || body && body.clientTop || 0);
  133. }
  134. // Add preventDefault method
  135. event.preventDefault = function() {
  136. event.isDefaultPrevented = returnTrue;
  137. // Execute preventDefault on the original event object
  138. if (originalEvent) {
  139. if (originalEvent.preventDefault) {
  140. originalEvent.preventDefault();
  141. } else {
  142. originalEvent.returnValue = false; // IE
  143. }
  144. }
  145. };
  146. // Add stopPropagation
  147. event.stopPropagation = function() {
  148. event.isPropagationStopped = returnTrue;
  149. // Execute stopPropagation on the original event object
  150. if (originalEvent) {
  151. if (originalEvent.stopPropagation) {
  152. originalEvent.stopPropagation();
  153. } else {
  154. originalEvent.cancelBubble = true; // IE
  155. }
  156. }
  157. };
  158. // Add stopImmediatePropagation
  159. event.stopImmediatePropagation = function() {
  160. event.isImmediatePropagationStopped = returnTrue;
  161. event.stopPropagation();
  162. };
  163. // Add event delegation states
  164. if (!event.isDefaultPrevented) {
  165. event.isDefaultPrevented = returnFalse;
  166. event.isPropagationStopped = returnFalse;
  167. event.isImmediatePropagationStopped = returnFalse;
  168. }
  169. return event;
  170. }
  171. /**
  172. * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
  173. * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
  174. */
  175. function bindOnReady(win, callback, eventUtils) {
  176. var doc = win.document, event = {type: 'ready'};
  177. if (eventUtils.domLoaded) {
  178. callback(event);
  179. return;
  180. }
  181. // Gets called when the DOM is ready
  182. function readyHandler() {
  183. if (!eventUtils.domLoaded) {
  184. eventUtils.domLoaded = true;
  185. callback(event);
  186. }
  187. }
  188. function waitForDomLoaded() {
  189. // Check complete or interactive state if there is a body
  190. // element on some iframes IE 8 will produce a null body
  191. if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
  192. removeEvent(doc, "readystatechange", waitForDomLoaded);
  193. readyHandler();
  194. }
  195. }
  196. function tryScroll() {
  197. try {
  198. // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
  199. // http://javascript.nwbox.com/IEContentLoaded/
  200. doc.documentElement.doScroll("left");
  201. } catch (ex) {
  202. setTimeout(tryScroll, 0);
  203. return;
  204. }
  205. readyHandler();
  206. }
  207. // Use W3C method
  208. if (doc.addEventListener) {
  209. if (doc.readyState === "complete") {
  210. readyHandler();
  211. } else {
  212. addEvent(win, 'DOMContentLoaded', readyHandler);
  213. }
  214. } else {
  215. // Use IE method
  216. addEvent(doc, "readystatechange", waitForDomLoaded);
  217. // Wait until we can scroll, when we can the DOM is initialized
  218. if (doc.documentElement.doScroll && win.self === win.top) {
  219. tryScroll();
  220. }
  221. }
  222. // Fallback if any of the above methods should fail for some odd reason
  223. addEvent(win, 'load', readyHandler);
  224. }
  225. /**
  226. * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
  227. */
  228. function EventUtils() {
  229. var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
  230. expando = eventExpandoPrefix + (+new Date()).toString(32);
  231. hasMouseEnterLeave = "onmouseenter" in document.documentElement;
  232. hasFocusIn = "onfocusin" in document.documentElement;
  233. mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
  234. count = 1;
  235. // State if the DOMContentLoaded was executed or not
  236. self.domLoaded = false;
  237. self.events = events;
  238. /**
  239. * Executes all event handler callbacks for a specific event.
  240. *
  241. * @private
  242. * @param {Event} evt Event object.
  243. * @param {String} id Expando id value to look for.
  244. */
  245. function executeHandlers(evt, id) {
  246. var callbackList, i, l, callback, container = events[id];
  247. callbackList = container && container[evt.type];
  248. if (callbackList) {
  249. for (i = 0, l = callbackList.length; i < l; i++) {
  250. callback = callbackList[i];
  251. // Check if callback exists might be removed if a unbind is called inside the callback
  252. if (callback && callback.func.call(callback.scope, evt) === false) {
  253. evt.preventDefault();
  254. }
  255. // Should we stop propagation to immediate listeners
  256. if (evt.isImmediatePropagationStopped()) {
  257. return;
  258. }
  259. }
  260. }
  261. }
  262. /**
  263. * Binds a callback to an event on the specified target.
  264. *
  265. * @method bind
  266. * @param {Object} target Target node/window or custom object.
  267. * @param {String} names Name of the event to bind.
  268. * @param {function} callback Callback function to execute when the event occurs.
  269. * @param {Object} scope Scope to call the callback function on, defaults to target.
  270. * @return {function} Callback function that got bound.
  271. */
  272. self.bind = function(target, names, callback, scope) {
  273. var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
  274. // Native event handler function patches the event and executes the callbacks for the expando
  275. function defaultNativeHandler(evt) {
  276. executeHandlers(fix(evt || win.event), id);
  277. }
  278. // Don't bind to text nodes or comments
  279. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  280. return;
  281. }
  282. // Create or get events id for the target
  283. if (!target[expando]) {
  284. id = count++;
  285. target[expando] = id;
  286. events[id] = {};
  287. } else {
  288. id = target[expando];
  289. }
  290. // Setup the specified scope or use the target as a default
  291. scope = scope || target;
  292. // Split names and bind each event, enables you to bind multiple events with one call
  293. names = names.split(' ');
  294. i = names.length;
  295. while (i--) {
  296. name = names[i];
  297. nativeHandler = defaultNativeHandler;
  298. fakeName = capture = false;
  299. // Use ready instead of DOMContentLoaded
  300. if (name === "DOMContentLoaded") {
  301. name = "ready";
  302. }
  303. // DOM is already ready
  304. if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
  305. callback.call(scope, fix({type: name}));
  306. continue;
  307. }
  308. // Handle mouseenter/mouseleaver
  309. if (!hasMouseEnterLeave) {
  310. fakeName = mouseEnterLeave[name];
  311. if (fakeName) {
  312. nativeHandler = function(evt) {
  313. var current, related;
  314. current = evt.currentTarget;
  315. related = evt.relatedTarget;
  316. // Check if related is inside the current target if it's not then the event should
  317. // be ignored since it's a mouseover/mouseout inside the element
  318. if (related && current.contains) {
  319. // Use contains for performance
  320. related = current.contains(related);
  321. } else {
  322. while (related && related !== current) {
  323. related = related.parentNode;
  324. }
  325. }
  326. // Fire fake event
  327. if (!related) {
  328. evt = fix(evt || win.event);
  329. evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
  330. evt.target = current;
  331. executeHandlers(evt, id);
  332. }
  333. };
  334. }
  335. }
  336. // Fake bubbeling of focusin/focusout
  337. if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
  338. capture = true;
  339. fakeName = name === "focusin" ? "focus" : "blur";
  340. nativeHandler = function(evt) {
  341. evt = fix(evt || win.event);
  342. evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
  343. executeHandlers(evt, id);
  344. };
  345. }
  346. // Setup callback list and bind native event
  347. callbackList = events[id][name];
  348. if (!callbackList) {
  349. events[id][name] = callbackList = [{func: callback, scope: scope}];
  350. callbackList.fakeName = fakeName;
  351. callbackList.capture = capture;
  352. // Add the nativeHandler to the callback list so that we can later unbind it
  353. callbackList.nativeHandler = nativeHandler;
  354. // Check if the target has native events support
  355. if (name === "ready") {
  356. bindOnReady(target, nativeHandler, self);
  357. } else {
  358. addEvent(target, fakeName || name, nativeHandler, capture);
  359. }
  360. } else {
  361. if (name === "ready" && self.domLoaded) {
  362. callback({type: name});
  363. } else {
  364. // If it already has an native handler then just push the callback
  365. callbackList.push({func: callback, scope: scope});
  366. }
  367. }
  368. }
  369. target = callbackList = 0; // Clean memory for IE
  370. return callback;
  371. };
  372. /**
  373. * Unbinds the specified event by name, name and callback or all events on the target.
  374. *
  375. * @method unbind
  376. * @param {Object} target Target node/window or custom object.
  377. * @param {String} names Optional event name to unbind.
  378. * @param {function} callback Optional callback function to unbind.
  379. * @return {EventUtils} Event utils instance.
  380. */
  381. self.unbind = function(target, names, callback) {
  382. var id, callbackList, i, ci, name, eventMap;
  383. // Don't bind to text nodes or comments
  384. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  385. return self;
  386. }
  387. // Unbind event or events if the target has the expando
  388. id = target[expando];
  389. if (id) {
  390. eventMap = events[id];
  391. // Specific callback
  392. if (names) {
  393. names = names.split(' ');
  394. i = names.length;
  395. while (i--) {
  396. name = names[i];
  397. callbackList = eventMap[name];
  398. // Unbind the event if it exists in the map
  399. if (callbackList) {
  400. // Remove specified callback
  401. if (callback) {
  402. ci = callbackList.length;
  403. while (ci--) {
  404. if (callbackList[ci].func === callback) {
  405. var nativeHandler = callbackList.nativeHandler;
  406. var fakeName = callbackList.fakeName, capture = callbackList.capture;
  407. // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
  408. callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
  409. callbackList.nativeHandler = nativeHandler;
  410. callbackList.fakeName = fakeName;
  411. callbackList.capture = capture;
  412. eventMap[name] = callbackList;
  413. }
  414. }
  415. }
  416. // Remove all callbacks if there isn't a specified callback or there is no callbacks left
  417. if (!callback || callbackList.length === 0) {
  418. delete eventMap[name];
  419. removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
  420. }
  421. }
  422. }
  423. } else {
  424. // All events for a specific element
  425. for (name in eventMap) {
  426. callbackList = eventMap[name];
  427. removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
  428. }
  429. eventMap = {};
  430. }
  431. // Check if object is empty, if it isn't then we won't remove the expando map
  432. for (name in eventMap) {
  433. return self;
  434. }
  435. // Delete event object
  436. delete events[id];
  437. // Remove expando from target
  438. try {
  439. // IE will fail here since it can't delete properties from window
  440. delete target[expando];
  441. } catch (ex) {
  442. // IE will set it to null
  443. target[expando] = null;
  444. }
  445. }
  446. return self;
  447. };
  448. /**
  449. * Fires the specified event on the specified target.
  450. *
  451. * @method fire
  452. * @param {Object} target Target node/window or custom object.
  453. * @param {String} name Event name to fire.
  454. * @param {Object} args Optional arguments to send to the observers.
  455. * @return {EventUtils} Event utils instance.
  456. */
  457. self.fire = function(target, name, args) {
  458. var id;
  459. // Don't bind to text nodes or comments
  460. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  461. return self;
  462. }
  463. // Build event object by patching the args
  464. args = fix(null, args);
  465. args.type = name;
  466. args.target = target;
  467. do {
  468. // Found an expando that means there is listeners to execute
  469. id = target[expando];
  470. if (id) {
  471. executeHandlers(args, id);
  472. }
  473. // Walk up the DOM
  474. target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
  475. } while (target && !args.isPropagationStopped());
  476. return self;
  477. };
  478. /**
  479. * Removes all bound event listeners for the specified target. This will also remove any bound
  480. * listeners to child nodes within that target.
  481. *
  482. * @method clean
  483. * @param {Object} target Target node/window object.
  484. * @return {EventUtils} Event utils instance.
  485. */
  486. self.clean = function(target) {
  487. var i, children, unbind = self.unbind;
  488. // Don't bind to text nodes or comments
  489. if (!target || target.nodeType === 3 || target.nodeType === 8) {
  490. return self;
  491. }
  492. // Unbind any element on the specificed target
  493. if (target[expando]) {
  494. unbind(target);
  495. }
  496. // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
  497. if (!target.getElementsByTagName) {
  498. target = target.document;
  499. }
  500. // Remove events from each child element
  501. if (target && target.getElementsByTagName) {
  502. unbind(target);
  503. children = target.getElementsByTagName('*');
  504. i = children.length;
  505. while (i--) {
  506. target = children[i];
  507. if (target[expando]) {
  508. unbind(target);
  509. }
  510. }
  511. }
  512. return self;
  513. };
  514. /**
  515. * Destroys the event object. Call this on IE to remove memory leaks.
  516. */
  517. self.destroy = function() {
  518. events = {};
  519. };
  520. // Legacy function for canceling events
  521. self.cancel = function(e) {
  522. if (e) {
  523. e.preventDefault();
  524. e.stopImmediatePropagation();
  525. }
  526. return false;
  527. };
  528. }
  529. EventUtils.Event = new EventUtils();
  530. EventUtils.Event.bind(window, 'ready', function() {});
  531. return EventUtils;
  532. });
  533. // Included from: js/tinymce/classes/dom/Sizzle.js
  534. /**
  535. * Sizzle.js
  536. *
  537. * Copyright, Moxiecode Systems AB
  538. * Released under LGPL License.
  539. *
  540. * License: http://www.tinymce.com/license
  541. * Contributing: http://www.tinymce.com/contributing
  542. *
  543. * @ignore-file
  544. */
  545. /*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */
  546. /*eslint dot-notation:0, no-empty:0, no-cond-assign:0, no-unused-expressions:0, new-cap:0, no-nested-ternary:0, func-style:0, no-bitwise: 0 */
  547. /*
  548. * Sizzle CSS Selector Engine
  549. * Copyright, The Dojo Foundation
  550. * Released under the MIT, BSD, and GPL Licenses.
  551. * More information: http://sizzlejs.com/
  552. */
  553. define("tinymce/dom/Sizzle", [], function() {
  554. var i,
  555. cachedruns,
  556. Expr,
  557. getText,
  558. isXML,
  559. compile,
  560. outermostContext,
  561. recompare,
  562. sortInput,
  563. // Local document vars
  564. setDocument,
  565. document,
  566. docElem,
  567. documentIsHTML,
  568. rbuggyQSA,
  569. rbuggyMatches,
  570. matches,
  571. contains,
  572. // Instance-specific data
  573. expando = "sizzle" + -(new Date()),
  574. preferredDoc = window.document,
  575. support = {},
  576. dirruns = 0,
  577. done = 0,
  578. classCache = createCache(),
  579. tokenCache = createCache(),
  580. compilerCache = createCache(),
  581. hasDuplicate = false,
  582. sortOrder = function() { return 0; },
  583. // General-purpose constants
  584. strundefined = typeof undefined,
  585. MAX_NEGATIVE = 1 << 31,
  586. // Array methods
  587. arr = [],
  588. pop = arr.pop,
  589. push_native = arr.push,
  590. push = arr.push,
  591. slice = arr.slice,
  592. // Use a stripped-down indexOf if we can't use a native one
  593. indexOf = arr.indexOf || function( elem ) {
  594. var i = 0,
  595. len = this.length;
  596. for ( ; i < len; i++ ) {
  597. if ( this[i] === elem ) {
  598. return i;
  599. }
  600. }
  601. return -1;
  602. },
  603. // Regular expressions
  604. // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
  605. whitespace = "[\\x20\\t\\r\\n\\f]",
  606. // http://www.w3.org/TR/css3-syntax/#characters
  607. characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
  608. // Loosely modeled on CSS identifier characters
  609. // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
  610. // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
  611. identifier = characterEncoding.replace( "w", "w#" ),
  612. // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
  613. operators = "([*^$|!~]?=)",
  614. attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
  615. "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
  616. // Prefer arguments quoted,
  617. // then not containing pseudos/brackets,
  618. // then attribute selectors/non-parenthetical expressions,
  619. // then anything else
  620. // These preferences are here to reduce the number of selectors
  621. // needing tokenize in the PSEUDO preFilter
  622. pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
  623. // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
  624. rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
  625. rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
  626. rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
  627. rpseudo = new RegExp( pseudos ),
  628. ridentifier = new RegExp( "^" + identifier + "$" ),
  629. matchExpr = {
  630. "ID": new RegExp( "^#(" + characterEncoding + ")" ),
  631. "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
  632. "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
  633. "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
  634. "ATTR": new RegExp( "^" + attributes ),
  635. "PSEUDO": new RegExp( "^" + pseudos ),
  636. "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
  637. "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
  638. "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
  639. // For use in libraries implementing .is()
  640. // We use this for POS matching in `select`
  641. "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
  642. whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
  643. },
  644. rsibling = /[\x20\t\r\n\f]*[+~]/,
  645. rnative = /^[^{]+\{\s*\[native code/,
  646. // Easily-parseable/retrievable ID or TAG or CLASS selectors
  647. rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
  648. rinputs = /^(?:input|select|textarea|button)$/i,
  649. rheader = /^h\d$/i,
  650. rescape = /'|\\/g,
  651. rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
  652. // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
  653. runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,
  654. funescape = function( _, escaped ) {
  655. var high = "0x" + escaped - 0x10000;
  656. // NaN means non-codepoint
  657. return high !== high ?
  658. escaped :
  659. // BMP codepoint
  660. high < 0 ?
  661. String.fromCharCode( high + 0x10000 ) :
  662. // Supplemental Plane codepoint (surrogate pair)
  663. String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
  664. };
  665. // Optimize for push.apply( _, NodeList )
  666. try {
  667. push.apply(
  668. (arr = slice.call( preferredDoc.childNodes )),
  669. preferredDoc.childNodes
  670. );
  671. // Support: Android<4.0
  672. // Detect silently failing push.apply
  673. arr[ preferredDoc.childNodes.length ].nodeType;
  674. } catch ( e ) {
  675. push = { apply: arr.length ?
  676. // Leverage slice if possible
  677. function( target, els ) {
  678. push_native.apply( target, slice.call(els) );
  679. } :
  680. // Support: IE<9
  681. // Otherwise append directly
  682. function( target, els ) {
  683. var j = target.length,
  684. i = 0;
  685. // Can't trust NodeList.length
  686. while ( (target[j++] = els[i++]) ) {}
  687. target.length = j - 1;
  688. }
  689. };
  690. }
  691. /**
  692. * For feature detection
  693. * @param {Function} fn The function to test for native support
  694. */
  695. function isNative( fn ) {
  696. return rnative.test( fn + "" );
  697. }
  698. /**
  699. * Create key-value caches of limited size
  700. * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
  701. * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
  702. * deleting the oldest entry
  703. */
  704. function createCache() {
  705. var cache,
  706. keys = [];
  707. cache = function( key, value ) {
  708. // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
  709. if ( keys.push( key += " " ) > Expr.cacheLength ) {
  710. // Only keep the most recent entries
  711. delete cache[ keys.shift() ];
  712. }
  713. cache[ key ] = value;
  714. return value;
  715. };
  716. return cache;
  717. }
  718. /**
  719. * Mark a function for special use by Sizzle
  720. * @param {Function} fn The function to mark
  721. */
  722. function markFunction( fn ) {
  723. fn[ expando ] = true;
  724. return fn;
  725. }
  726. /**
  727. * Support testing using an element
  728. * @param {Function} fn Passed the created div and expects a boolean result
  729. */
  730. function assert( fn ) {
  731. var div = document.createElement("div");
  732. try {
  733. return !!fn( div );
  734. } catch (e) {
  735. return false;
  736. } finally {
  737. // release memory in IE
  738. div = null;
  739. }
  740. }
  741. function Sizzle( selector, context, results, seed ) {
  742. var match, elem, m, nodeType,
  743. // QSA vars
  744. i, groups, old, nid, newContext, newSelector;
  745. if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
  746. setDocument( context );
  747. }
  748. context = context || document;
  749. results = results || [];
  750. if ( !selector || typeof selector !== "string" ) {
  751. return results;
  752. }
  753. if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
  754. return [];
  755. }
  756. if ( documentIsHTML && !seed ) {
  757. // Shortcuts
  758. if ( (match = rquickExpr.exec( selector )) ) {
  759. // Speed-up: Sizzle("#ID")
  760. if ( (m = match[1]) ) {
  761. if ( nodeType === 9 ) {
  762. elem = context.getElementById( m );
  763. // Check parentNode to catch when Blackberry 4.6 returns
  764. // nodes that are no longer in the document #6963
  765. if ( elem && elem.parentNode ) {
  766. // Handle the case where IE, Opera, and Webkit return items
  767. // by name instead of ID
  768. if ( elem.id === m ) {
  769. results.push( elem );
  770. return results;
  771. }
  772. } else {
  773. return results;
  774. }
  775. } else {
  776. // Context is not a document
  777. if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
  778. contains( context, elem ) && elem.id === m ) {
  779. results.push( elem );
  780. return results;
  781. }
  782. }
  783. // Speed-up: Sizzle("TAG")
  784. } else if ( match[2] ) {
  785. push.apply( results, context.getElementsByTagName( selector ) );
  786. return results;
  787. // Speed-up: Sizzle(".CLASS")
  788. } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
  789. push.apply( results, context.getElementsByClassName( m ) );
  790. return results;
  791. }
  792. }
  793. // QSA path
  794. if ( support.qsa && !rbuggyQSA.test(selector) ) {
  795. old = true;
  796. nid = expando;
  797. newContext = context;
  798. newSelector = nodeType === 9 && selector;
  799. // qSA works strangely on Element-rooted queries
  800. // We can work around this by specifying an extra ID on the root
  801. // and working up from there (Thanks to Andrew Dupont for the technique)
  802. // IE 8 doesn't work on object elements
  803. if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
  804. groups = tokenize( selector );
  805. if ( (old = context.getAttribute("id")) ) {
  806. nid = old.replace( rescape, "\\$&" );
  807. } else {
  808. context.setAttribute( "id", nid );
  809. }
  810. nid = "[id='" + nid + "'] ";
  811. i = groups.length;
  812. while ( i-- ) {
  813. groups[i] = nid + toSelector( groups[i] );
  814. }
  815. newContext = rsibling.test( selector ) && context.parentNode || context;
  816. newSelector = groups.join(",");
  817. }
  818. if ( newSelector ) {
  819. try {
  820. push.apply( results,
  821. newContext.querySelectorAll( newSelector )
  822. );
  823. return results;
  824. } catch(qsaError) {
  825. } finally {
  826. if ( !old ) {
  827. context.removeAttribute("id");
  828. }
  829. }
  830. }
  831. }
  832. }
  833. // All others
  834. return select( selector.replace( rtrim, "$1" ), context, results, seed );
  835. }
  836. /**
  837. * Detect xml
  838. * @param {Element|Object} elem An element or a document
  839. */
  840. isXML = Sizzle.isXML = function( elem ) {
  841. // documentElement is verified for cases where it doesn't yet exist
  842. // (such as loading iframes in IE - #4833)
  843. var documentElement = elem && (elem.ownerDocument || elem).documentElement;
  844. return documentElement ? documentElement.nodeName !== "HTML" : false;
  845. };
  846. /**
  847. * Sets document-related variables once based on the current document
  848. * @param {Element|Object} [doc] An element or document object to use to set the document
  849. * @returns {Object} Returns the current document
  850. */
  851. setDocument = Sizzle.setDocument = function( node ) {
  852. var doc = node ? node.ownerDocument || node : preferredDoc;
  853. // If no document and documentElement is available, return
  854. if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
  855. return document;
  856. }
  857. // Set our document
  858. document = doc;
  859. docElem = doc.documentElement;
  860. // Support tests
  861. documentIsHTML = !isXML( doc );
  862. // Check if getElementsByTagName("*") returns only elements
  863. support.getElementsByTagName = assert(function( div ) {
  864. div.appendChild( doc.createComment("") );
  865. return !div.getElementsByTagName("*").length;
  866. });
  867. // Check if attributes should be retrieved by attribute nodes
  868. support.attributes = assert(function( div ) {
  869. div.innerHTML = "<select></select>";
  870. var type = typeof div.lastChild.getAttribute("multiple");
  871. // IE8 returns a string for some attributes even when not present
  872. return type !== "boolean" && type !== "string";
  873. });
  874. // Check if getElementsByClassName can be trusted
  875. support.getElementsByClassName = assert(function( div ) {
  876. // Opera can't find a second classname (in 9.6)
  877. div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
  878. if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
  879. return false;
  880. }
  881. // Safari 3.2 caches class attributes and doesn't catch changes
  882. div.lastChild.className = "e";
  883. return div.getElementsByClassName("e").length === 2;
  884. });
  885. // Check if getElementsByName privileges form controls or returns elements by ID
  886. // If so, assume (for broader support) that getElementById returns elements by name
  887. support.getByName = assert(function( div ) {
  888. // Inject content
  889. div.id = expando + 0;
  890. // Support: Windows 8 Native Apps
  891. // Assigning innerHTML with "name" attributes throws uncatchable exceptions
  892. // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx
  893. div.appendChild( document.createElement("a") ).setAttribute( "name", expando );
  894. div.appendChild( document.createElement("i") ).setAttribute( "name", expando );
  895. docElem.appendChild( div );
  896. // Test
  897. var pass = doc.getElementsByName &&
  898. // buggy browsers will return fewer than the correct 2
  899. doc.getElementsByName( expando ).length === 2 +
  900. // buggy browsers will return more than the correct 0
  901. doc.getElementsByName( expando + 0 ).length;
  902. // Cleanup
  903. docElem.removeChild( div );
  904. return pass;
  905. });
  906. // Support: Webkit<537.32
  907. // Detached nodes confoundingly follow *each other*
  908. support.sortDetached = assert(function( div1 ) {
  909. return div1.compareDocumentPosition &&
  910. // Should return 1, but Webkit returns 4 (following)
  911. (div1.compareDocumentPosition( document.createElement("div") ) & 1);
  912. });
  913. // IE6/7 return modified attributes
  914. Expr.attrHandle = assert(function( div ) {
  915. div.innerHTML = "<a href='#'></a>";
  916. return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
  917. div.firstChild.getAttribute("href") === "#";
  918. }) ?
  919. {} :
  920. {
  921. "href": function( elem ) {
  922. return elem.getAttribute( "href", 2 );
  923. },
  924. "type": function( elem ) {
  925. return elem.getAttribute("type");
  926. }
  927. };
  928. // ID find and filter
  929. if ( support.getByName ) {
  930. Expr.find["ID"] = function( id, context ) {
  931. if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
  932. var m = context.getElementById( id );
  933. // Check parentNode to catch when Blackberry 4.6 returns
  934. // nodes that are no longer in the document #6963
  935. return m && m.parentNode ? [m] : [];
  936. }
  937. };
  938. Expr.filter["ID"] = function( id ) {
  939. var attrId = id.replace( runescape, funescape );
  940. return function( elem ) {
  941. return elem.getAttribute("id") === attrId;
  942. };
  943. };
  944. } else {
  945. Expr.find["ID"] = function( id, context ) {
  946. if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
  947. var m = context.getElementById( id );
  948. return m ?
  949. m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
  950. [m] :
  951. undefined :
  952. [];
  953. }
  954. };
  955. Expr.filter["ID"] = function( id ) {
  956. var attrId = id.replace( runescape, funescape );
  957. return function( elem ) {
  958. var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
  959. return node && node.value === attrId;
  960. };
  961. };
  962. }
  963. // Tag
  964. Expr.find["TAG"] = support.getElementsByTagName ?
  965. function( tag, context ) {
  966. if ( typeof context.getElementsByTagName !== strundefined ) {
  967. return context.getElementsByTagName( tag );
  968. }
  969. } :
  970. function( tag, context ) {
  971. var elem,
  972. tmp = [],
  973. i = 0,
  974. results = context.getElementsByTagName( tag );
  975. // Filter out possible comments
  976. if ( tag === "*" ) {
  977. while ( (elem = results[i++]) ) {
  978. if ( elem.nodeType === 1 ) {
  979. tmp.push( elem );
  980. }
  981. }
  982. return tmp;
  983. }
  984. return results;
  985. };
  986. // Name
  987. Expr.find["NAME"] = support.getByName && function( tag, context ) {
  988. if ( typeof context.getElementsByName !== strundefined ) {
  989. return context.getElementsByName( name );
  990. }
  991. };
  992. // Class
  993. Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
  994. if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
  995. return context.getElementsByClassName( className );
  996. }
  997. };
  998. // QSA and matchesSelector support
  999. // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
  1000. rbuggyMatches = [];
  1001. // qSa(:focus) reports false when true (Chrome 21),
  1002. // no need to also add to buggyMatches since matches checks buggyQSA
  1003. // A support test would require too much code (would include document ready)
  1004. rbuggyQSA = [ ":focus" ];
  1005. if ( (support.qsa = isNative(doc.querySelectorAll)) ) {
  1006. // Build QSA regex
  1007. // Regex strategy adopted from Diego Perini
  1008. assert(function( div ) {
  1009. // Select is set to empty string on purpose
  1010. // This is to test IE's treatment of not explicitly
  1011. // setting a boolean content attribute,
  1012. // since its presence should be enough
  1013. // http://bugs.jquery.com/ticket/12359
  1014. div.innerHTML = "<select><option selected=''></option></select>";
  1015. // IE8 - Some boolean attributes are not treated correctly
  1016. if ( !div.querySelectorAll("[selected]").length ) {
  1017. rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
  1018. }
  1019. // Webkit/Opera - :checked should return selected option elements
  1020. // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
  1021. // IE8 throws error here and will not see later tests
  1022. if ( !div.querySelectorAll(":checked").length ) {
  1023. rbuggyQSA.push(":checked");
  1024. }
  1025. });
  1026. assert(function( div ) {
  1027. // Opera 10-12/IE8 - ^= $= *= and empty values
  1028. // Should not select anything
  1029. div.innerHTML = "<input type='hidden' i=''/>";
  1030. if ( div.querySelectorAll("[i^='']").length ) {
  1031. rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
  1032. }
  1033. // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
  1034. // IE8 throws error here and will not see later tests
  1035. if ( !div.querySelectorAll(":enabled").length ) {
  1036. rbuggyQSA.push( ":enabled", ":disabled" );
  1037. }
  1038. // Opera 10-11 does not throw on post-comma invalid pseudos
  1039. div.querySelectorAll("*,:x");
  1040. rbuggyQSA.push(",.*:");
  1041. });
  1042. }
  1043. if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector ||
  1044. docElem.mozMatchesSelector ||
  1045. docElem.webkitMatchesSelector ||
  1046. docElem.oMatchesSelector ||
  1047. docElem.msMatchesSelector) )) ) {
  1048. assert(function( div ) {
  1049. // Check to see if it's possible to do matchesSelector
  1050. // on a disconnected node (IE 9)
  1051. support.disconnectedMatch = matches.call( div, "div" );
  1052. // This should fail with an exception
  1053. // Gecko does not error, returns false instead
  1054. matches.call( div, "[s!='']:x" );
  1055. rbuggyMatches.push( "!=", pseudos );
  1056. });
  1057. }
  1058. rbuggyQSA = new RegExp( rbuggyQSA.join("|") );
  1059. rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
  1060. // Element contains another
  1061. // Purposefully does not implement inclusive descendant
  1062. // As in, an element does not contain itself
  1063. contains = isNative(docElem.contains) || docElem.compareDocumentPosition ?
  1064. function( a, b ) {
  1065. var adown = a.nodeType === 9 ? a.documentElement : a,
  1066. bup = b && b.parentNode;
  1067. return a === bup || !!( bup && bup.nodeType === 1 && (
  1068. adown.contains ?
  1069. adown.contains( bup ) :
  1070. a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
  1071. ));
  1072. } :
  1073. function( a, b ) {
  1074. if ( b ) {
  1075. while ( (b = b.parentNode) ) {
  1076. if ( b === a ) {
  1077. return true;
  1078. }
  1079. }
  1080. }
  1081. return false;
  1082. };
  1083. // Document order sorting
  1084. sortOrder = docElem.compareDocumentPosition ?
  1085. function( a, b ) {
  1086. // Flag for duplicate removal
  1087. if ( a === b ) {
  1088. hasDuplicate = true;
  1089. return 0;
  1090. }
  1091. var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
  1092. if ( compare ) {
  1093. // Disconnected nodes
  1094. if ( compare & 1 ||
  1095. (recompare && b.compareDocumentPosition( a ) === compare) ) {
  1096. // Choose the first element that is related to our preferred document
  1097. if ( a === doc || contains(preferredDoc, a) ) {
  1098. return -1;
  1099. }
  1100. if ( b === doc || contains(preferredDoc, b) ) {
  1101. return 1;
  1102. }
  1103. // Maintain original order
  1104. return sortInput ?
  1105. ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
  1106. 0;
  1107. }
  1108. return compare & 4 ? -1 : 1;
  1109. }
  1110. // Not directly comparable, sort on existence of method
  1111. return a.compareDocumentPosition ? -1 : 1;
  1112. } :
  1113. function( a, b ) {
  1114. var cur,
  1115. i = 0,
  1116. aup = a.parentNode,
  1117. bup = b.parentNode,
  1118. ap = [ a ],
  1119. bp = [ b ];
  1120. // Exit early if the nodes are identical
  1121. if ( a === b ) {
  1122. hasDuplicate = true;
  1123. return 0;
  1124. // Parentless nodes are either documents or disconnected
  1125. } else if ( !aup || !bup ) {
  1126. return a === doc ? -1 :
  1127. b === doc ? 1 :
  1128. aup ? -1 :
  1129. bup ? 1 :
  1130. 0;
  1131. // If the nodes are siblings, we can do a quick check
  1132. } else if ( aup === bup ) {
  1133. return siblingCheck( a, b );
  1134. }
  1135. // Otherwise we need full lists of their ancestors for comparison
  1136. cur = a;
  1137. while ( (cur = cur.parentNode) ) {
  1138. ap.unshift( cur );
  1139. }
  1140. cur = b;
  1141. while ( (cur = cur.parentNode) ) {
  1142. bp.unshift( cur );
  1143. }
  1144. // Walk down the tree looking for a discrepancy
  1145. while ( ap[i] === bp[i] ) {
  1146. i++;
  1147. }
  1148. return i ?
  1149. // Do a sibling check if the nodes have a common ancestor
  1150. siblingCheck( ap[i], bp[i] ) :
  1151. // Otherwise nodes in our document sort first
  1152. ap[i] === preferredDoc ? -1 :
  1153. bp[i] === preferredDoc ? 1 :
  1154. 0;
  1155. };
  1156. return document;
  1157. };
  1158. Sizzle.matches = function( expr, elements ) {
  1159. return Sizzle( expr, null, null, elements );
  1160. };
  1161. Sizzle.matchesSelector = function( elem, expr ) {
  1162. // Set document vars if needed
  1163. if ( ( elem.ownerDocument || elem ) !== document ) {
  1164. setDocument( elem );
  1165. }
  1166. // Make sure that attribute selectors are quoted
  1167. expr = expr.replace( rattributeQuotes, "='$1']" );
  1168. // rbuggyQSA always contains :focus, so no need for an existence check
  1169. if ( support.matchesSelector && documentIsHTML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) {
  1170. try {
  1171. var ret = matches.call( elem, expr );
  1172. // IE 9's matchesSelector returns false on disconnected nodes
  1173. if ( ret || support.disconnectedMatch ||
  1174. // As well, disconnected nodes are said to be in a document
  1175. // fragment in IE 9
  1176. elem.document && elem.document.nodeType !== 11 ) {
  1177. return ret;
  1178. }
  1179. } catch(e) {}
  1180. }
  1181. return Sizzle( expr, document, null, [elem] ).length > 0;
  1182. };
  1183. Sizzle.contains = function( context, elem ) {
  1184. // Set document vars if needed
  1185. if ( ( context.ownerDocument || context ) !== document ) {
  1186. setDocument( context );
  1187. }
  1188. return contains( context, elem );
  1189. };
  1190. Sizzle.attr = function( elem, name ) {
  1191. var val;
  1192. // Set document vars if needed
  1193. if ( ( elem.ownerDocument || elem ) !== document ) {
  1194. setDocument( elem );
  1195. }
  1196. if ( documentIsHTML ) {
  1197. name = name.toLowerCase();
  1198. }
  1199. if ( (val = Expr.attrHandle[ name ]) ) {
  1200. return val( elem );
  1201. }
  1202. if ( !documentIsHTML || support.attributes ) {
  1203. return elem.getAttribute( name );
  1204. }
  1205. return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ?
  1206. name :
  1207. val && val.specified ? val.value : null;
  1208. };
  1209. Sizzle.error = function( msg ) {
  1210. throw new Error( "Syntax error, unrecognized expression: " + msg );
  1211. };
  1212. // Document sorting and removing duplicates
  1213. Sizzle.uniqueSort = function( results ) {
  1214. var elem,
  1215. duplicates = [],
  1216. j = 0,
  1217. i = 0;
  1218. // Unless we *know* we can detect duplicates, assume their presence
  1219. hasDuplicate = !support.detectDuplicates;
  1220. // Compensate for sort limitations
  1221. recompare = !support.sortDetached;
  1222. sortInput = !support.sortStable && results.slice( 0 );
  1223. results.sort( sortOrder );
  1224. if ( hasDuplicate ) {
  1225. while ( (elem = results[i++]) ) {
  1226. if ( elem === results[ i ] ) {
  1227. j = duplicates.push( i );
  1228. }
  1229. }
  1230. while ( j-- ) {
  1231. results.splice( duplicates[ j ], 1 );
  1232. }
  1233. }
  1234. return results;
  1235. };
  1236. /**
  1237. * Checks document order of two siblings
  1238. * @param {Element} a
  1239. * @param {Element} b
  1240. * @returns Returns -1 if a precedes b, 1 if a follows b
  1241. */
  1242. function siblingCheck( a, b ) {
  1243. var cur = b && a,
  1244. diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE );
  1245. // Use IE sourceIndex if available on both nodes
  1246. if ( diff ) {
  1247. return diff;
  1248. }
  1249. // Check if b follows a
  1250. if ( cur ) {
  1251. while ( (cur = cur.nextSibling) ) {
  1252. if ( cur === b ) {
  1253. return -1;
  1254. }
  1255. }
  1256. }
  1257. return a ? 1 : -1;
  1258. }
  1259. // Returns a function to use in pseudos for input types
  1260. function createInputPseudo( type ) {
  1261. return function( elem ) {
  1262. var name = elem.nodeName.toLowerCase();
  1263. return name === "input" && elem.type === type;
  1264. };
  1265. }
  1266. // Returns a function to use in pseudos for buttons
  1267. function createButtonPseudo( type ) {
  1268. return function( elem ) {
  1269. var name = elem.nodeName.toLowerCase();
  1270. return (name === "input" || name === "button") && elem.type === type;
  1271. };
  1272. }
  1273. // Returns a function to use in pseudos for positionals
  1274. function createPositionalPseudo( fn ) {
  1275. return markFunction(function( argument ) {
  1276. argument = +argument;
  1277. return markFunction(function( seed, matches ) {
  1278. var j,
  1279. matchIndexes = fn( [], seed.length, argument ),
  1280. i = matchIndexes.length;
  1281. // Match elements found at the specified indexes
  1282. while ( i-- ) {
  1283. if ( seed[ (j = matchIndexes[i]) ] ) {
  1284. seed[j] = !(matches[j] = seed[j]);
  1285. }
  1286. }
  1287. });
  1288. });
  1289. }
  1290. /**
  1291. * Utility function for retrieving the text value of an array of DOM nodes
  1292. * @param {Array|Element} elem
  1293. */
  1294. getText = Sizzle.getText = function( elem ) {
  1295. var node,
  1296. ret = "",
  1297. i = 0,
  1298. nodeType = elem.nodeType;
  1299. if ( !nodeType ) {
  1300. // If no nodeType, this is expected to be an array
  1301. for ( ; (node = elem[i]); i++ ) {
  1302. // Do not traverse comment nodes
  1303. ret += getText( node );
  1304. }
  1305. } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
  1306. // Use textContent for elements
  1307. // innerText usage removed for consistency of new lines (see #11153)
  1308. if ( typeof elem.textContent === "string" ) {
  1309. return elem.textContent;
  1310. } else {
  1311. // Traverse its children
  1312. for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
  1313. ret += getText( elem );
  1314. }
  1315. }
  1316. } else if ( nodeType === 3 || nodeType === 4 ) {
  1317. return elem.nodeValue;
  1318. }
  1319. // Do not include comment or processing instruction nodes
  1320. return ret;
  1321. };
  1322. Expr = Sizzle.selectors = {
  1323. // Can be adjusted by the user
  1324. cacheLength: 50,
  1325. createPseudo: markFunction,
  1326. match: matchExpr,
  1327. find: {},
  1328. relative: {
  1329. ">": { dir: "parentNode", first: true },
  1330. " ": { dir: "parentNode" },
  1331. "+": { dir: "previousSibling", first: true },
  1332. "~": { dir: "previousSibling" }
  1333. },
  1334. preFilter: {
  1335. "ATTR": function( match ) {
  1336. match[1] = match[1].replace( runescape, funescape );
  1337. // Move the given value to match[3] whether quoted or unquoted
  1338. match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
  1339. if ( match[2] === "~=" ) {
  1340. match[3] = " " + match[3] + " ";
  1341. }
  1342. return match.slice( 0, 4 );
  1343. },
  1344. "CHILD": function( match ) {
  1345. /* matches from matchExpr["CHILD"]
  1346. 1 type (only|nth|...)
  1347. 2 what (child|of-type)
  1348. 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
  1349. 4 xn-component of xn+y argument ([+-]?\d*n|)
  1350. 5 sign of xn-component
  1351. 6 x of xn-component
  1352. 7 sign of y-component
  1353. 8 y of y-component
  1354. */
  1355. match[1] = match[1].toLowerCase();
  1356. if ( match[1].slice( 0, 3 ) === "nth" ) {
  1357. // nth-* requires argument
  1358. if ( !match[3] ) {
  1359. Sizzle.error( match[0] );
  1360. }
  1361. // numeric x and y parameters for Expr.filter.CHILD
  1362. // remember that false/true cast respectively to 0/1
  1363. match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
  1364. match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
  1365. // other types prohibit arguments
  1366. } else if ( match[3] ) {
  1367. Sizzle.error( match[0] );
  1368. }
  1369. return match;
  1370. },
  1371. "PSEUDO": function( match ) {
  1372. var excess,
  1373. unquoted = !match[5] && match[2];
  1374. if ( matchExpr["CHILD"].test( match[0] ) ) {
  1375. return null;
  1376. }
  1377. // Accept quoted arguments as-is
  1378. if ( match[4] ) {
  1379. match[2] = match[4];
  1380. // Strip excess characters from unquoted arguments
  1381. } else if ( unquoted && rpseudo.test( unquoted ) &&
  1382. // Get excess from tokenize (recursively)
  1383. (excess = tokenize( unquoted, true )) &&
  1384. // advance to the next closing parenthesis
  1385. (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
  1386. // excess is a negative index
  1387. match[0] = match[0].slice( 0, excess );
  1388. match[2] = unquoted.slice( 0, excess );
  1389. }
  1390. // Return only captures needed by the pseudo filter method (type and argument)
  1391. return match.slice( 0, 3 );
  1392. }
  1393. },
  1394. filter: {
  1395. "TAG": function( nodeName ) {
  1396. if ( nodeName === "*" ) {
  1397. return function() { return true; };
  1398. }
  1399. nodeName = nodeName.replace( runescape, funescape ).toLowerCase();
  1400. return function( elem ) {
  1401. return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
  1402. };
  1403. },
  1404. "CLASS": function( className ) {
  1405. var pattern = classCache[ className + " " ];
  1406. return pattern ||
  1407. (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
  1408. classCache( className, function( elem ) {
  1409. return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
  1410. });
  1411. },
  1412. "ATTR": function( name, operator, check ) {
  1413. return function( elem ) {
  1414. var result = Sizzle.attr( elem, name );
  1415. if ( result == null ) {
  1416. return operator === "!=";
  1417. }
  1418. if ( !operator ) {
  1419. return true;
  1420. }
  1421. result += "";
  1422. return operator === "=" ? result === check :
  1423. operator === "!=" ? result !== check :
  1424. operator === "^=" ? check && result.indexOf( check ) === 0 :
  1425. operator === "*=" ? check && result.indexOf( check ) > -1 :
  1426. operator === "$=" ? check && result.slice( -check.length ) === check :
  1427. operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
  1428. operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
  1429. false;
  1430. };
  1431. },
  1432. "CHILD": function( type, what, argument, first, last ) {
  1433. var simple = type.slice( 0, 3 ) !== "nth",
  1434. forward = type.slice( -4 ) !== "last",
  1435. ofType = what === "of-type";
  1436. return first === 1 && last === 0 ?
  1437. // Shortcut for :nth-*(n)
  1438. function( elem ) {
  1439. return !!elem.parentNode;
  1440. } :
  1441. function( elem, context, xml ) {
  1442. var cache, outerCache, node, diff, nodeIndex, start,
  1443. dir = simple !== forward ? "nextSibling" : "previousSibling",
  1444. parent = elem.parentNode,
  1445. name = ofType && elem.nodeName.toLowerCase(),
  1446. useCache = !xml && !ofType;
  1447. if ( parent ) {
  1448. // :(first|last|only)-(child|of-type)
  1449. if ( simple ) {
  1450. while ( dir ) {
  1451. node = elem;
  1452. while ( (node = node[ dir ]) ) {
  1453. if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
  1454. return false;
  1455. }
  1456. }
  1457. // Reverse direction for :only-* (if we haven't yet done so)
  1458. start = dir = type === "only" && !start && "nextSibling";
  1459. }
  1460. return true;
  1461. }
  1462. start = [ forward ? parent.firstChild : parent.lastChild ];
  1463. // non-xml :nth-child(...) stores cache data on `parent`
  1464. if ( forward && useCache ) {
  1465. // Seek `elem` from a previously-cached index
  1466. outerCache = parent[ expando ] || (parent[ expando ] = {});
  1467. cache = outerCache[ type ] || [];
  1468. nodeIndex = cache[0] === dirruns && cache[1];
  1469. diff = cache[0] === dirruns && cache[2];
  1470. node = nodeIndex && parent.childNodes[ nodeIndex ];
  1471. while ( (node = ++nodeIndex && node && node[ dir ] ||
  1472. // Fallback to seeking `elem` from the start
  1473. (diff = nodeIndex = 0) || start.pop()) ) {
  1474. // When found, cache indexes on `parent` and break
  1475. if ( node.nodeType === 1 && ++diff && node === elem ) {
  1476. outerCache[ type ] = [ dirruns, nodeIndex, diff ];
  1477. break;
  1478. }
  1479. }
  1480. // Use previously-cached element index if available
  1481. } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
  1482. diff = cache[1];
  1483. // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
  1484. } else {
  1485. // Use the same loop as above to seek `elem` from the start
  1486. while ( (node = ++nodeIndex && node && node[ dir ] ||
  1487. (diff = nodeIndex = 0) || start.pop()) ) {
  1488. if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
  1489. // Cache the index of each encountered element
  1490. if ( useCache ) {
  1491. (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
  1492. }
  1493. if ( node === elem ) {
  1494. break;
  1495. }
  1496. }
  1497. }
  1498. }
  1499. // Incorporate the offset, then check against cycle size
  1500. diff -= last;
  1501. return diff === first || ( diff % first === 0 && diff / first >= 0 );
  1502. }
  1503. };
  1504. },
  1505. "PSEUDO": function( pseudo, argument ) {
  1506. // pseudo-class names are case-insensitive
  1507. // http://www.w3.org/TR/selectors/#pseudo-classes
  1508. // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
  1509. // Remember that setFilters inherits from pseudos
  1510. var args,
  1511. fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
  1512. Sizzle.error( "unsupported pseudo: " + pseudo );
  1513. // The user may use createPseudo to indicate that
  1514. // arguments are needed to create the filter function
  1515. // just as Sizzle does
  1516. if ( fn[ expando ] ) {
  1517. return fn( argument );
  1518. }
  1519. // But maintain support for old signatures
  1520. if ( fn.length > 1 ) {
  1521. args = [ pseudo, pseudo, "", argument ];
  1522. return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
  1523. markFunction(function( seed, matches ) {
  1524. var idx,
  1525. matched = fn( seed, argument ),
  1526. i = matched.length;
  1527. while ( i-- ) {
  1528. idx = indexOf.call( seed, matched[i] );
  1529. seed[ idx ] = !( matches[ idx ] = matched[i] );
  1530. }
  1531. }) :
  1532. function( elem ) {
  1533. return fn( elem, 0, args );
  1534. };
  1535. }
  1536. return fn;
  1537. }
  1538. },
  1539. pseudos: {
  1540. // Potentially complex pseudos
  1541. "not": markFunction(function( selector ) {
  1542. // Trim the selector passed to compile
  1543. // to avoid treating leading and trailing
  1544. // spaces as combinators
  1545. var input = [],
  1546. results = [],
  1547. matcher = compile( selector.replace( rtrim, "$1" ) );
  1548. return matcher[ expando ] ?
  1549. markFunction(function( seed, matches, context, xml ) {
  1550. var elem,
  1551. unmatched = matcher( seed, null, xml, [] ),
  1552. i = seed.length;
  1553. // Match elements unmatched by `matcher`
  1554. while ( i-- ) {
  1555. if ( (elem = unmatched[i]) ) {
  1556. seed[i] = !(matches[i] = elem);
  1557. }
  1558. }
  1559. }) :
  1560. function( elem, context, xml ) {
  1561. input[0] = elem;
  1562. matcher( input, null, xml, results );
  1563. return !results.pop();
  1564. };
  1565. }),
  1566. "has": markFunction(function( selector ) {
  1567. return function( elem ) {
  1568. return Sizzle( selector, elem ).length > 0;
  1569. };
  1570. }),
  1571. "contains": markFunction(function( text ) {
  1572. return function( elem ) {
  1573. return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
  1574. };
  1575. }),
  1576. // "Whether an element is represented by a :lang() selector
  1577. // is based solely on the element's language value
  1578. // being equal to the identifier C,
  1579. // or beginning with the identifier C immediately followed by "-".
  1580. // The matching of C against the element's language value is performed case-insensitively.
  1581. // The identifier C does not have to be a valid language name."
  1582. // http://www.w3.org/TR/selectors/#lang-pseudo
  1583. "lang": markFunction( function( lang ) {
  1584. // lang value must be a valid identifier
  1585. if ( !ridentifier.test(lang || "") ) {
  1586. Sizzle.error( "unsupported lang: " + lang );
  1587. }
  1588. lang = lang.replace( runescape, funescape ).toLowerCase();
  1589. return function( elem ) {
  1590. var elemLang;
  1591. do {
  1592. if ( (elemLang = documentIsHTML ?
  1593. elem.lang :
  1594. elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
  1595. elemLang = elemLang.toLowerCase();
  1596. return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
  1597. }
  1598. } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
  1599. return false;
  1600. };
  1601. }),
  1602. // Miscellaneous
  1603. "target": function( elem ) {
  1604. var hash = window.location && window.location.hash;
  1605. return hash && hash.slice( 1 ) === elem.id;
  1606. },
  1607. "root": function( elem ) {
  1608. return elem === docElem;
  1609. },
  1610. "focus": function( elem ) {
  1611. return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
  1612. },
  1613. // Boolean properties
  1614. "enabled": function( elem ) {
  1615. return elem.disabled === false;
  1616. },
  1617. "disabled": function( elem ) {
  1618. return elem.disabled === true;
  1619. },
  1620. "checked": function( elem ) {
  1621. // In CSS3, :checked should return both checked and selected elements
  1622. // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
  1623. var nodeName = elem.nodeName.toLowerCase();
  1624. return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
  1625. },
  1626. "selected": function( elem ) {
  1627. // Accessing this property makes selected-by-default
  1628. // options in Safari work properly
  1629. if ( elem.parentNode ) {
  1630. elem.parentNode.selectedIndex;
  1631. }
  1632. return elem.selected === true;
  1633. },
  1634. // Contents
  1635. "empty": function( elem ) {
  1636. // http://www.w3.org/TR/selectors/#empty-pseudo
  1637. // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
  1638. // not comment, processing instructions, or others
  1639. // Thanks to Diego Perini for the nodeName shortcut
  1640. // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
  1641. for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
  1642. if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
  1643. return false;
  1644. }
  1645. }
  1646. return true;
  1647. },
  1648. "parent": function( elem ) {
  1649. return !Expr.pseudos["empty"]( elem );
  1650. },
  1651. // Element/input types
  1652. "header": function( elem ) {
  1653. return rheader.test( elem.nodeName );
  1654. },
  1655. "input": function( elem ) {
  1656. return rinputs.test( elem.nodeName );
  1657. },
  1658. "button": function( elem ) {
  1659. var name = elem.nodeName.toLowerCase();
  1660. return name === "input" && elem.type === "button" || name === "button";
  1661. },
  1662. "text": function( elem ) {
  1663. var attr;
  1664. // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
  1665. // use getAttribute instead to test this case
  1666. return elem.nodeName.toLowerCase() === "input" &&
  1667. elem.type === "text" &&
  1668. ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
  1669. },
  1670. // Position-in-collection
  1671. "first": createPositionalPseudo(function() {
  1672. return [ 0 ];
  1673. }),
  1674. "last": createPositionalPseudo(function( matchIndexes, length ) {
  1675. return [ length - 1 ];
  1676. }),
  1677. "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
  1678. return [ argument < 0 ? argument + length : argument ];
  1679. }),
  1680. "even": createPositionalPseudo(function( matchIndexes, length ) {
  1681. var i = 0;
  1682. for ( ; i < length; i += 2 ) {
  1683. matchIndexes.push( i );
  1684. }
  1685. return matchIndexes;
  1686. }),
  1687. "odd": createPositionalPseudo(function( matchIndexes, length ) {
  1688. var i = 1;
  1689. for ( ; i < length; i += 2 ) {
  1690. matchIndexes.push( i );
  1691. }
  1692. return matchIndexes;
  1693. }),
  1694. "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
  1695. var i = argument < 0 ? argument + length : argument;
  1696. for ( ; --i >= 0; ) {
  1697. matchIndexes.push( i );
  1698. }
  1699. return matchIndexes;
  1700. }),
  1701. "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
  1702. var i = argument < 0 ? argument + length : argument;
  1703. for ( ; ++i < length; ) {
  1704. matchIndexes.push( i );
  1705. }
  1706. return matchIndexes;
  1707. })
  1708. }
  1709. };
  1710. // Add button/input type pseudos
  1711. for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
  1712. Expr.pseudos[ i ] = createInputPseudo( i );
  1713. }
  1714. for ( i in { submit: true, reset: true } ) {
  1715. Expr.pseudos[ i ] = createButtonPseudo( i );
  1716. }
  1717. function tokenize( selector, parseOnly ) {
  1718. var matched, match, tokens, type,
  1719. soFar, groups, preFilters,
  1720. cached = tokenCache[ selector + " " ];
  1721. if ( cached ) {
  1722. return parseOnly ? 0 : cached.slice( 0 );
  1723. }
  1724. soFar = selector;
  1725. groups = [];
  1726. preFilters = Expr.preFilter;
  1727. while ( soFar ) {
  1728. // Comma and first run
  1729. if ( !matched || (match = rcomma.exec( soFar )) ) {
  1730. if ( match ) {
  1731. // Don't consume trailing commas as valid
  1732. soFar = soFar.slice( match[0].length ) || soFar;
  1733. }
  1734. groups.push( tokens = [] );
  1735. }
  1736. matched = false;
  1737. // Combinators
  1738. if ( (match = rcombinators.exec( soFar )) ) {
  1739. matched = match.shift();
  1740. tokens.push( {
  1741. value: matched,
  1742. // Cast descendant combinators to space
  1743. type: match[0].replace( rtrim, " " )
  1744. } );
  1745. soFar = soFar.slice( matched.length );
  1746. }
  1747. // Filters
  1748. for ( type in Expr.filter ) {
  1749. if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
  1750. (match = preFilters[ type ]( match ))) ) {
  1751. matched = match.shift();
  1752. tokens.push( {
  1753. value: matched,
  1754. type: type,
  1755. matches: match
  1756. } );
  1757. soFar = soFar.slice( matched.length );
  1758. }
  1759. }
  1760. if ( !matched ) {
  1761. break;
  1762. }
  1763. }
  1764. // Return the length of the invalid excess
  1765. // if we're just parsing
  1766. // Otherwise, throw an error or return tokens
  1767. return parseOnly ?
  1768. soFar.length :
  1769. soFar ?
  1770. Sizzle.error( selector ) :
  1771. // Cache the tokens
  1772. tokenCache( selector, groups ).slice( 0 );
  1773. }
  1774. function toSelector( tokens ) {
  1775. var i = 0,
  1776. len = tokens.length,
  1777. selector = "";
  1778. for ( ; i < len; i++ ) {
  1779. selector += tokens[i].value;
  1780. }
  1781. return selector;
  1782. }
  1783. function addCombinator( matcher, combinator, base ) {
  1784. var dir = combinator.dir,
  1785. checkNonElements = base && dir === "parentNode",
  1786. doneName = done++;
  1787. return combinator.first ?
  1788. // Check against closest ancestor/preceding element
  1789. function( elem, context, xml ) {
  1790. while ( (elem = elem[ dir ]) ) {
  1791. if ( elem.nodeType === 1 || checkNonElements ) {
  1792. return matcher( elem, context, xml );
  1793. }
  1794. }
  1795. } :
  1796. // Check against all ancestor/preceding elements
  1797. function( elem, context, xml ) {
  1798. var data, cache, outerCache,
  1799. dirkey = dirruns + " " + doneName;
  1800. // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
  1801. if ( xml ) {
  1802. while ( (elem = elem[ dir ]) ) {
  1803. if ( elem.nodeType === 1 || checkNonElements ) {
  1804. if ( matcher( elem, context, xml ) ) {
  1805. return true;
  1806. }
  1807. }
  1808. }
  1809. } else {
  1810. while ( (elem = elem[ dir ]) ) {
  1811. if ( elem.nodeType === 1 || checkNonElements ) {
  1812. outerCache = elem[ expando ] || (elem[ expando ] = {});
  1813. if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
  1814. if ( (data = cache[1]) === true || data === cachedruns ) {
  1815. return data === true;
  1816. }
  1817. } else {
  1818. cache = outerCache[ dir ] = [ dirkey ];
  1819. cache[1] = matcher( elem, context, xml ) || cachedruns;
  1820. if ( cache[1] === true ) {
  1821. return true;
  1822. }
  1823. }
  1824. }
  1825. }
  1826. }
  1827. };
  1828. }
  1829. function elementMatcher( matchers ) {
  1830. return matchers.length > 1 ?
  1831. function( elem, context, xml ) {
  1832. var i = matchers.length;
  1833. while ( i-- ) {
  1834. if ( !matchers[i]( elem, context, xml ) ) {
  1835. return false;
  1836. }
  1837. }
  1838. return true;
  1839. } :
  1840. matchers[0];
  1841. }
  1842. function condense( unmatched, map, filter, context, xml ) {
  1843. var elem,
  1844. newUnmatched = [],
  1845. i = 0,
  1846. len = unmatched.length,
  1847. mapped = map != null;
  1848. for ( ; i < len; i++ ) {
  1849. if ( (elem = unmatched[i]) ) {
  1850. if ( !filter || filter( elem, context, xml ) ) {
  1851. newUnmatched.push( elem );
  1852. if ( mapped ) {
  1853. map.push( i );
  1854. }
  1855. }
  1856. }
  1857. }
  1858. return newUnmatched;
  1859. }
  1860. function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
  1861. if ( postFilter && !postFilter[ expando ] ) {
  1862. postFilter = setMatcher( postFilter );
  1863. }
  1864. if ( postFinder && !postFinder[ expando ] ) {
  1865. postFinder = setMatcher( postFinder, postSelector );
  1866. }
  1867. return markFunction(function( seed, results, context, xml ) {
  1868. var temp, i, elem,
  1869. preMap = [],
  1870. postMap = [],
  1871. preexisting = results.length,
  1872. // Get initial elements from seed or context
  1873. elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
  1874. // Prefilter to get matcher input, preserving a map for seed-results synchronization
  1875. matcherIn = preFilter && ( seed || !selector ) ?
  1876. condense( elems, preMap, preFilter, context, xml ) :
  1877. elems,
  1878. matcherOut = matcher ?
  1879. // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
  1880. postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
  1881. // ...intermediate processing is necessary
  1882. [] :
  1883. // ...otherwise use results directly
  1884. results :
  1885. matcherIn;
  1886. // Find primary matches
  1887. if ( matcher ) {
  1888. matcher( matcherIn, matcherOut, context, xml );
  1889. }
  1890. // Apply postFilter
  1891. if ( postFilter ) {
  1892. temp = condense( matcherOut, postMap );
  1893. postFilter( temp, [], context, xml );
  1894. // Un-match failing elements by moving them back to matcherIn
  1895. i = temp.length;
  1896. while ( i-- ) {
  1897. if ( (elem = temp[i]) ) {
  1898. matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
  1899. }
  1900. }
  1901. }
  1902. if ( seed ) {
  1903. if ( postFinder || preFilter ) {
  1904. if ( postFinder ) {
  1905. // Get the final matcherOut by condensing this intermediate into postFinder contexts
  1906. temp = [];
  1907. i = matcherOut.length;
  1908. while ( i-- ) {
  1909. if ( (elem = matcherOut[i]) ) {
  1910. // Restore matcherIn since elem is not yet a final match
  1911. temp.push( (matcherIn[i] = elem) );
  1912. }
  1913. }
  1914. postFinder( null, (matcherOut = []), temp, xml );
  1915. }
  1916. // Move matched elements from seed to results to keep them synchronized
  1917. i = matcherOut.length;
  1918. while ( i-- ) {
  1919. if ( (elem = matcherOut[i]) &&
  1920. (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
  1921. seed[temp] = !(results[temp] = elem);
  1922. }
  1923. }
  1924. }
  1925. // Add elements to results, through postFinder if defined
  1926. } else {
  1927. matcherOut = condense(
  1928. matcherOut === results ?
  1929. matcherOut.splice( preexisting, matcherOut.length ) :
  1930. matcherOut
  1931. );
  1932. if ( postFinder ) {
  1933. postFinder( null, results, matcherOut, xml );
  1934. } else {
  1935. push.apply( results, matcherOut );
  1936. }
  1937. }
  1938. });
  1939. }
  1940. function matcherFromTokens( tokens ) {
  1941. var checkContext, matcher, j,
  1942. len = tokens.length,
  1943. leadingRelative = Expr.relative[ tokens[0].type ],
  1944. implicitRelative = leadingRelative || Expr.relative[" "],
  1945. i = leadingRelative ? 1 : 0,
  1946. // The foundational matcher ensures that elements are reachable from top-level context(s)
  1947. matchContext = addCombinator( function( elem ) {
  1948. return elem === checkContext;
  1949. }, implicitRelative, true ),
  1950. matchAnyContext = addCombinator( function( elem ) {
  1951. return indexOf.call( checkContext, elem ) > -1;
  1952. }, implicitRelative, true ),
  1953. matchers = [ function( elem, context, xml ) {
  1954. return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
  1955. (checkContext = context).nodeType ?
  1956. matchContext( elem, context, xml ) :
  1957. matchAnyContext( elem, context, xml ) );
  1958. } ];
  1959. for ( ; i < len; i++ ) {
  1960. if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
  1961. matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
  1962. } else {
  1963. matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
  1964. // Return special upon seeing a positional matcher
  1965. if ( matcher[ expando ] ) {
  1966. // Find the next relative operator (if any) for proper handling
  1967. j = ++i;
  1968. for ( ; j < len; j++ ) {
  1969. if ( Expr.relative[ tokens[j].type ] ) {
  1970. break;
  1971. }
  1972. }
  1973. return setMatcher(
  1974. i > 1 && elementMatcher( matchers ),
  1975. i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ),
  1976. matcher,
  1977. i < j && matcherFromTokens( tokens.slice( i, j ) ),
  1978. j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
  1979. j < len && toSelector( tokens )
  1980. );
  1981. }
  1982. matchers.push( matcher );
  1983. }
  1984. }
  1985. return elementMatcher( matchers );
  1986. }
  1987. function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
  1988. // A counter to specify which element is currently being matched
  1989. var matcherCachedRuns = 0,
  1990. bySet = setMatchers.length > 0,
  1991. byElement = elementMatchers.length > 0,
  1992. superMatcher = function( seed, context, xml, results, expandContext ) {
  1993. var elem, j, matcher,
  1994. setMatched = [],
  1995. matchedCount = 0,
  1996. i = "0",
  1997. unmatched = seed && [],
  1998. outermost = expandContext != null,
  1999. contextBackup = outermostContext,
  2000. // We must always have either seed elements or context
  2001. elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
  2002. // Use integer dirruns iff this is the outermost matcher
  2003. dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
  2004. if ( outermost ) {
  2005. outermostContext = context !== document && context;
  2006. cachedruns = matcherCachedRuns;
  2007. }
  2008. // Add elements passing elementMatchers directly to results
  2009. // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
  2010. for ( ; (elem = elems[i]) != null; i++ ) {
  2011. if ( byElement && elem ) {
  2012. j = 0;
  2013. while ( (matcher = elementMatchers[j++]) ) {
  2014. if ( matcher( elem, context, xml ) ) {
  2015. results.push( elem );
  2016. break;
  2017. }
  2018. }
  2019. if ( outermost ) {
  2020. dirruns = dirrunsUnique;
  2021. cachedruns = ++matcherCachedRuns;
  2022. }
  2023. }
  2024. // Track unmatched elements for set filters
  2025. if ( bySet ) {
  2026. // They will have gone through all possible matchers
  2027. if ( (elem = !matcher && elem) ) {
  2028. matchedCount--;
  2029. }
  2030. // Lengthen the array for every element, matched or not
  2031. if ( seed ) {
  2032. unmatched.push( elem );
  2033. }
  2034. }
  2035. }
  2036. // Apply set filters to unmatched elements
  2037. matchedCount += i;
  2038. if ( bySet && i !== matchedCount ) {
  2039. j = 0;
  2040. while ( (matcher = setMatchers[j++]) ) {
  2041. matcher( unmatched, setMatched, context, xml );
  2042. }
  2043. if ( seed ) {
  2044. // Reintegrate element matches to eliminate the need for sorting
  2045. if ( matchedCount > 0 ) {
  2046. while ( i-- ) {
  2047. if ( !(unmatched[i] || setMatched[i]) ) {
  2048. setMatched[i] = pop.call( results );
  2049. }
  2050. }
  2051. }
  2052. // Discard index placeholder values to get only actual matches
  2053. setMatched = condense( setMatched );
  2054. }
  2055. // Add matches to results
  2056. push.apply( results, setMatched );
  2057. // Seedless set matches succeeding multiple successful matchers stipulate sorting
  2058. if ( outermost && !seed && setMatched.length > 0 &&
  2059. ( matchedCount + setMatchers.length ) > 1 ) {
  2060. Sizzle.uniqueSort( results );
  2061. }
  2062. }
  2063. // Override manipulation of globals by nested matchers
  2064. if ( outermost ) {
  2065. dirruns = dirrunsUnique;
  2066. outermostContext = contextBackup;
  2067. }
  2068. return unmatched;
  2069. };
  2070. return bySet ?
  2071. markFunction( superMatcher ) :
  2072. superMatcher;
  2073. }
  2074. compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
  2075. var i,
  2076. setMatchers = [],
  2077. elementMatchers = [],
  2078. cached = compilerCache[ selector + " " ];
  2079. if ( !cached ) {
  2080. // Generate a function of recursive functions that can be used to check each element
  2081. if ( !group ) {
  2082. group = tokenize( selector );
  2083. }
  2084. i = group.length;
  2085. while ( i-- ) {
  2086. cached = matcherFromTokens( group[i] );
  2087. if ( cached[ expando ] ) {
  2088. setMatchers.push( cached );
  2089. } else {
  2090. elementMatchers.push( cached );
  2091. }
  2092. }
  2093. // Cache the compiled function
  2094. cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
  2095. }
  2096. return cached;
  2097. };
  2098. function multipleContexts( selector, contexts, results ) {
  2099. var i = 0,
  2100. len = contexts.length;
  2101. for ( ; i < len; i++ ) {
  2102. Sizzle( selector, contexts[i], results );
  2103. }
  2104. return results;
  2105. }
  2106. function select( selector, context, results, seed ) {
  2107. var i, tokens, token, type, find,
  2108. match = tokenize( selector );
  2109. if ( !seed ) {
  2110. // Try to minimize operations if there is only one group
  2111. if ( match.length === 1 ) {
  2112. // Take a shortcut and set the context if the root selector is an ID
  2113. tokens = match[0] = match[0].slice( 0 );
  2114. if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
  2115. context.nodeType === 9 && documentIsHTML &&
  2116. Expr.relative[ tokens[1].type ] ) {
  2117. context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
  2118. if ( !context ) {
  2119. return results;
  2120. }
  2121. selector = selector.slice( tokens.shift().value.length );
  2122. }
  2123. // Fetch a seed set for right-to-left matching
  2124. i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
  2125. while ( i-- ) {
  2126. token = tokens[i];
  2127. // Abort if we hit a combinator
  2128. if ( Expr.relative[ (type = token.type) ] ) {
  2129. break;
  2130. }
  2131. if ( (find = Expr.find[ type ]) ) {
  2132. // Search, expanding context for leading sibling combinators
  2133. if ( (seed = find(
  2134. token.matches[0].replace( runescape, funescape ),
  2135. rsibling.test( tokens[0].type ) && context.parentNode || context
  2136. )) ) {
  2137. // If seed is empty or no tokens remain, we can return early
  2138. tokens.splice( i, 1 );
  2139. selector = seed.length && toSelector( tokens );
  2140. if ( !selector ) {
  2141. push.apply( results, seed );
  2142. return results;
  2143. }
  2144. break;
  2145. }
  2146. }
  2147. }
  2148. }
  2149. }
  2150. // Compile and execute a filtering function
  2151. // Provide `match` to avoid retokenization if we modified the selector above
  2152. compile( selector, match )(
  2153. seed,
  2154. context,
  2155. !documentIsHTML,
  2156. results,
  2157. rsibling.test( selector )
  2158. );
  2159. return results;
  2160. }
  2161. // Deprecated
  2162. Expr.pseudos["nth"] = Expr.pseudos["eq"];
  2163. // Easy API for creating new setFilters
  2164. function setFilters() {}
  2165. setFilters.prototype = Expr.filters = Expr.pseudos;
  2166. Expr.setFilters = new setFilters();
  2167. // Check sort stability
  2168. support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
  2169. // Initialize with the default document
  2170. setDocument();
  2171. // Always assume the presence of duplicates if sort doesn't
  2172. // pass them to our comparison function (as in Google Chrome).
  2173. [0, 0].sort( sortOrder );
  2174. support.detectDuplicates = hasDuplicate;
  2175. /*
  2176. // EXPOSE
  2177. if ( typeof define === "function" && define.amd ) {
  2178. define(function() { return Sizzle; });
  2179. } else {
  2180. window.Sizzle = Sizzle;
  2181. }
  2182. */
  2183. // EXPOSE
  2184. return Sizzle;
  2185. });
  2186. // Included from: js/tinymce/classes/dom/DomQuery.js
  2187. /**
  2188. * DomQuery.js
  2189. *
  2190. * Copyright, Moxiecode Systems AB
  2191. * Released under LGPL License.
  2192. *
  2193. * License: http://www.tinymce.com/license
  2194. * Contributing: http://www.tinymce.com/contributing
  2195. *
  2196. * Some of this logic is based on jQuery code that is released under
  2197. * MIT license that grants us to sublicense it under LGPL.
  2198. *
  2199. * @ignore-file
  2200. */
  2201. /**
  2202. * @class tinymce.dom.DomQuery
  2203. */
  2204. define("tinymce/dom/DomQuery", [
  2205. "tinymce/dom/EventUtils",
  2206. "tinymce/dom/Sizzle"
  2207. ], function(EventUtils, Sizzle) {
  2208. var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
  2209. var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
  2210. var Event = EventUtils.Event;
  2211. function isDefined(obj) {
  2212. return typeof obj !== "undefined";
  2213. }
  2214. function isString(obj) {
  2215. return typeof obj === "string";
  2216. }
  2217. function createFragment(html) {
  2218. var frag, node, container;
  2219. container = doc.createElement("div");
  2220. frag = doc.createDocumentFragment();
  2221. container.innerHTML = html;
  2222. while ((node = container.firstChild)) {
  2223. frag.appendChild(node);
  2224. }
  2225. return frag;
  2226. }
  2227. function domManipulate(targetNodes, sourceItem, callback) {
  2228. var i;
  2229. if (typeof sourceItem === "string") {
  2230. sourceItem = createFragment(sourceItem);
  2231. } else if (sourceItem.length) {
  2232. for (i = 0; i < sourceItem.length; i++) {
  2233. domManipulate(targetNodes, sourceItem[i], callback);
  2234. }
  2235. return targetNodes;
  2236. }
  2237. i = targetNodes.length;
  2238. while (i--) {
  2239. callback.call(targetNodes[i], sourceItem.parentNode ? sourceItem : sourceItem);
  2240. }
  2241. return targetNodes;
  2242. }
  2243. function hasClass(node, className) {
  2244. return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
  2245. }
  2246. /**
  2247. * Makes a map object out of a string that gets separated by a delimiter.
  2248. *
  2249. * @method makeMap
  2250. * @param {String} items Item string to split.
  2251. * @param {Object} map Optional object to add items to.
  2252. * @return {Object} name/value object with items as keys.
  2253. */
  2254. function makeMap(items, map) {
  2255. var i;
  2256. items = items || [];
  2257. if (typeof(items) == "string") {
  2258. items = items.split(' ');
  2259. }
  2260. map = map || {};
  2261. i = items.length;
  2262. while (i--) {
  2263. map[items[i]] = {};
  2264. }
  2265. return map;
  2266. }
  2267. var numericCssMap = makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom');
  2268. function DomQuery(selector, context) {
  2269. /*eslint new-cap:0 */
  2270. return new DomQuery.fn.init(selector, context);
  2271. }
  2272. /**
  2273. * Extends the specified object with another object.
  2274. *
  2275. * @method extend
  2276. * @param {Object} target Object to extend.
  2277. * @param {Object..} obj Multiple objects to extend with.
  2278. * @return {Object} Same as target, the extended object.
  2279. */
  2280. function extend(target) {
  2281. var args = arguments, arg, i, key;
  2282. for (i = 1; i < args.length; i++) {
  2283. arg = args[i];
  2284. for (key in arg) {
  2285. target[key] = arg[key];
  2286. }
  2287. }
  2288. return target;
  2289. }
  2290. /**
  2291. * Converts the specified object into a real JavaScript array.
  2292. *
  2293. * @method toArray
  2294. * @param {Object} obj Object to convert into array.
  2295. * @return {Array} Array object based in input.
  2296. */
  2297. function toArray(obj) {
  2298. var array = [], i, l;
  2299. for (i = 0, l = obj.length; i < l; i++) {
  2300. array[i] = obj[i];
  2301. }
  2302. return array;
  2303. }
  2304. /**
  2305. * Returns the index of the specified item inside the array.
  2306. *
  2307. * @method inArray
  2308. * @param {Object} item Item to look for.
  2309. * @param {Array} array Array to look for item in.
  2310. * @return {Number} Index of the item or -1.
  2311. */
  2312. function inArray(item, array) {
  2313. var i;
  2314. if (array.indexOf) {
  2315. return array.indexOf(item);
  2316. }
  2317. i = array.length;
  2318. while (i--) {
  2319. if (array[i] === item) {
  2320. return i;
  2321. }
  2322. }
  2323. return -1;
  2324. }
  2325. /**
  2326. * Returns true/false if the specified object is an array.
  2327. *
  2328. * @method isArray
  2329. * @param {Object} obj Object to check if it's an array.
  2330. * @return {Boolean} true/false if the input object is array or not.
  2331. */
  2332. var isArray = Array.isArray || function(obj) {
  2333. return Object.prototype.toString.call(obj) === "[object Array]";
  2334. };
  2335. var whiteSpaceRegExp = /^\s*|\s*$/g;
  2336. function trim(str) {
  2337. return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  2338. }
  2339. /**
  2340. * Executes the callback function for each item in array/object. If you return false in the
  2341. * callback it will break the loop.
  2342. *
  2343. * @method each
  2344. * @param {Object} obj Object to iterate.
  2345. * @param {function} callback Callback function to execute for each item.
  2346. */
  2347. function each(obj, callback) {
  2348. var length, key, i, undef, value;
  2349. if (obj) {
  2350. length = obj.length;
  2351. if (length === undef) {
  2352. // Loop object items
  2353. for (key in obj) {
  2354. if (obj.hasOwnProperty(key)) {
  2355. value = obj[key];
  2356. if (callback.call(value, value, key) === false) {
  2357. break;
  2358. }
  2359. }
  2360. }
  2361. } else {
  2362. // Loop array items
  2363. for (i = 0; i < length; i++) {
  2364. value = obj[i];
  2365. if (callback.call(value, value, key) === false) {
  2366. break;
  2367. }
  2368. }
  2369. }
  2370. }
  2371. return obj;
  2372. }
  2373. DomQuery.fn = DomQuery.prototype = {
  2374. constructor: DomQuery,
  2375. selector: "",
  2376. length: 0,
  2377. init: function(selector, context) {
  2378. var self = this, match, node;
  2379. if (!selector) {
  2380. return self;
  2381. }
  2382. if (selector.nodeType) {
  2383. self.context = self[0] = selector;
  2384. self.length = 1;
  2385. return self;
  2386. }
  2387. if (isString(selector)) {
  2388. if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
  2389. match = [null, selector, null];
  2390. } else {
  2391. match = rquickExpr.exec(selector);
  2392. }
  2393. if (match) {
  2394. if (match[1]) {
  2395. node = createFragment(selector).firstChild;
  2396. while (node) {
  2397. this.add(node);
  2398. node = node.nextSibling;
  2399. }
  2400. } else {
  2401. node = doc.getElementById(match[2]);
  2402. if (node.id !== match[2]) {
  2403. return self.find(selector);
  2404. }
  2405. self.length = 1;
  2406. self[0] = node;
  2407. }
  2408. } else {
  2409. return DomQuery(context || document).find(selector);
  2410. }
  2411. } else {
  2412. this.add(selector);
  2413. }
  2414. return self;
  2415. },
  2416. toArray: function() {
  2417. return toArray(this);
  2418. },
  2419. add: function(items) {
  2420. var self = this;
  2421. // Force single item into array
  2422. if (!isArray(items)) {
  2423. if (items instanceof DomQuery) {
  2424. self.add(items.toArray());
  2425. } else {
  2426. push.call(self, items);
  2427. }
  2428. } else {
  2429. push.apply(self, items);
  2430. }
  2431. return self;
  2432. },
  2433. attr: function(name, value) {
  2434. var self = this;
  2435. if (typeof name === "object") {
  2436. each(name, function(value, name) {
  2437. self.attr(name, value);
  2438. });
  2439. } else if (isDefined(value)) {
  2440. this.each(function() {
  2441. if (this.nodeType === 1) {
  2442. this.setAttribute(name, value);
  2443. }
  2444. });
  2445. } else {
  2446. return self[0] && self[0].nodeType === 1 ? self[0].getAttribute(name) : undefined;
  2447. }
  2448. return self;
  2449. },
  2450. css: function(name, value) {
  2451. var self = this;
  2452. if (typeof name === "object") {
  2453. each(name, function(value, name) {
  2454. self.css(name, value);
  2455. });
  2456. } else {
  2457. // Camelcase it, if needed
  2458. name = name.replace(/-(\D)/g, function(a, b) {
  2459. return b.toUpperCase();
  2460. });
  2461. if (isDefined(value)) {
  2462. // Default px suffix on these
  2463. if (typeof(value) === 'number' && !numericCssMap[name]) {
  2464. value += 'px';
  2465. }
  2466. self.each(function() {
  2467. var style = this.style;
  2468. // IE specific opacity
  2469. if (name === "opacity" && this.runtimeStyle && typeof(this.runtimeStyle.opacity) === "undefined") {
  2470. style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
  2471. }
  2472. try {
  2473. style[name] = value;
  2474. } catch (ex) {
  2475. // Ignore
  2476. }
  2477. });
  2478. } else {
  2479. return self[0] ? self[0].style[name] : undefined;
  2480. }
  2481. }
  2482. return self;
  2483. },
  2484. remove: function() {
  2485. var self = this, node, i = this.length;
  2486. while (i--) {
  2487. node = self[i];
  2488. Event.clean(node);
  2489. if (node.parentNode) {
  2490. node.parentNode.removeChild(node);
  2491. }
  2492. }
  2493. return this;
  2494. },
  2495. empty: function() {
  2496. var self = this, node, i = this.length;
  2497. while (i--) {
  2498. node = self[i];
  2499. while (node.firstChild) {
  2500. node.removeChild(node.firstChild);
  2501. }
  2502. }
  2503. return this;
  2504. },
  2505. html: function(value) {
  2506. var self = this, i;
  2507. if (isDefined(value)) {
  2508. i = self.length;
  2509. while (i--) {
  2510. self[i].innerHTML = value;
  2511. }
  2512. return self;
  2513. }
  2514. return self[0] ? self[0].innerHTML : '';
  2515. },
  2516. text: function(value) {
  2517. var self = this, i;
  2518. if (isDefined(value)) {
  2519. i = self.length;
  2520. while (i--) {
  2521. self[i].innerText = self[0].textContent = value;
  2522. }
  2523. return self;
  2524. }
  2525. return self[0] ? self[0].innerText || self[0].textContent : '';
  2526. },
  2527. append: function() {
  2528. return domManipulate(this, arguments, function(node) {
  2529. if (this.nodeType === 1) {
  2530. this.appendChild(node);
  2531. }
  2532. });
  2533. },
  2534. prepend: function() {
  2535. return domManipulate(this, arguments, function(node) {
  2536. if (this.nodeType === 1) {
  2537. this.insertBefore(node, this.firstChild);
  2538. }
  2539. });
  2540. },
  2541. before: function() {
  2542. var self = this;
  2543. if (self[0] && self[0].parentNode) {
  2544. return domManipulate(self, arguments, function(node) {
  2545. this.parentNode.insertBefore(node, this.nextSibling);
  2546. });
  2547. }
  2548. return self;
  2549. },
  2550. after: function() {
  2551. var self = this;
  2552. if (self[0] && self[0].parentNode) {
  2553. return domManipulate(self, arguments, function(node) {
  2554. this.parentNode.insertBefore(node, this);
  2555. });
  2556. }
  2557. return self;
  2558. },
  2559. appendTo: function(val) {
  2560. DomQuery(val).append(this);
  2561. return this;
  2562. },
  2563. addClass: function(className) {
  2564. return this.toggleClass(className, true);
  2565. },
  2566. removeClass: function(className) {
  2567. return this.toggleClass(className, false);
  2568. },
  2569. toggleClass: function(className, state) {
  2570. var self = this;
  2571. if (className.indexOf(' ') !== -1) {
  2572. each(className.split(' '), function() {
  2573. self.toggleClass(this, state);
  2574. });
  2575. } else {
  2576. self.each(function(node) {
  2577. var existingClassName;
  2578. if (hasClass(node, className) !== state) {
  2579. existingClassName = node.className;
  2580. if (state) {
  2581. node.className += existingClassName ? ' ' + className : className;
  2582. } else {
  2583. node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
  2584. }
  2585. }
  2586. });
  2587. }
  2588. return self;
  2589. },
  2590. hasClass: function(className) {
  2591. return hasClass(this[0], className);
  2592. },
  2593. each: function(callback) {
  2594. return each(this, callback);
  2595. },
  2596. on: function(name, callback) {
  2597. return this.each(function() {
  2598. Event.bind(this, name, callback);
  2599. });
  2600. },
  2601. off: function(name, callback) {
  2602. return this.each(function() {
  2603. Event.unbind(this, name, callback);
  2604. });
  2605. },
  2606. show: function() {
  2607. return this.css('display', '');
  2608. },
  2609. hide: function() {
  2610. return this.css('display', 'none');
  2611. },
  2612. slice: function() {
  2613. return new DomQuery(slice.apply(this, arguments));
  2614. },
  2615. eq: function(index) {
  2616. return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  2617. },
  2618. first: function() {
  2619. return this.eq(0);
  2620. },
  2621. last: function() {
  2622. return this.eq(-1);
  2623. },
  2624. replaceWith: function(content) {
  2625. var self = this;
  2626. if (self[0]) {
  2627. self[0].parentNode.replaceChild(DomQuery(content)[0], self[0]);
  2628. }
  2629. return self;
  2630. },
  2631. wrap: function(wrapper) {
  2632. wrapper = DomQuery(wrapper)[0];
  2633. return this.each(function() {
  2634. var self = this, newWrapper = wrapper.cloneNode(false);
  2635. self.parentNode.insertBefore(newWrapper, self);
  2636. newWrapper.appendChild(self);
  2637. });
  2638. },
  2639. unwrap: function() {
  2640. return this.each(function() {
  2641. var self = this, node = self.firstChild, currentNode;
  2642. while (node) {
  2643. currentNode = node;
  2644. node = node.nextSibling;
  2645. self.parentNode.insertBefore(currentNode, self);
  2646. }
  2647. });
  2648. },
  2649. clone: function() {
  2650. var result = [];
  2651. this.each(function() {
  2652. result.push(this.cloneNode(true));
  2653. });
  2654. return DomQuery(result);
  2655. },
  2656. find: function(selector) {
  2657. var i, l, ret = [];
  2658. for (i = 0, l = this.length; i < l; i++) {
  2659. DomQuery.find(selector, this[i], ret);
  2660. }
  2661. return DomQuery(ret);
  2662. },
  2663. push: push,
  2664. sort: [].sort,
  2665. splice: [].splice
  2666. };
  2667. // Static members
  2668. extend(DomQuery, {
  2669. extend: extend,
  2670. toArray: toArray,
  2671. inArray: inArray,
  2672. isArray: isArray,
  2673. each: each,
  2674. trim: trim,
  2675. makeMap: makeMap,
  2676. // Sizzle
  2677. find: Sizzle,
  2678. expr: Sizzle.selectors,
  2679. unique: Sizzle.uniqueSort,
  2680. text: Sizzle.getText,
  2681. isXMLDoc: Sizzle.isXML,
  2682. contains: Sizzle.contains,
  2683. filter: function(expr, elems, not) {
  2684. if (not) {
  2685. expr = ":not(" + expr + ")";
  2686. }
  2687. if (elems.length === 1) {
  2688. elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
  2689. } else {
  2690. elems = DomQuery.find.matches(expr, elems);
  2691. }
  2692. return elems;
  2693. }
  2694. });
  2695. function dir(el, prop, until) {
  2696. var matched = [], cur = el[prop];
  2697. while (cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !DomQuery(cur).is(until))) {
  2698. if (cur.nodeType === 1) {
  2699. matched.push(cur);
  2700. }
  2701. cur = cur[prop];
  2702. }
  2703. return matched;
  2704. }
  2705. function sibling(n, el, siblingName, nodeType) {
  2706. var r = [];
  2707. for(; n; n = n[siblingName]) {
  2708. if ((!nodeType || n.nodeType === nodeType) && n !== el) {
  2709. r.push(n);
  2710. }
  2711. }
  2712. return r;
  2713. }
  2714. each({
  2715. parent: function(node) {
  2716. var parent = node.parentNode;
  2717. return parent && parent.nodeType !== 11 ? parent : null;
  2718. },
  2719. parents: function(node) {
  2720. return dir(node, "parentNode");
  2721. },
  2722. parentsUntil: function(node, until) {
  2723. return dir(node, "parentNode", until);
  2724. },
  2725. next: function(node) {
  2726. return sibling(node, 'nextSibling', 1);
  2727. },
  2728. prev: function(node) {
  2729. return sibling(node, 'previousSibling', 1);
  2730. },
  2731. nextNodes: function(node) {
  2732. return sibling(node, 'nextSibling');
  2733. },
  2734. prevNodes: function(node) {
  2735. return sibling(node, 'previousSibling');
  2736. },
  2737. children: function(node) {
  2738. return sibling(node.firstChild, 'nextSibling', 1);
  2739. },
  2740. contents: function(node) {
  2741. return toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
  2742. }
  2743. }, function(name, fn){
  2744. DomQuery.fn[name] = function(selector) {
  2745. var self = this, result;
  2746. if (self.length > 1) {
  2747. throw new Error("DomQuery only supports traverse functions on a single node.");
  2748. }
  2749. if (self[0]) {
  2750. result = fn(self[0], selector);
  2751. }
  2752. result = DomQuery(result);
  2753. if (selector && name !== "parentsUntil") {
  2754. return result.filter(selector);
  2755. }
  2756. return result;
  2757. };
  2758. });
  2759. DomQuery.fn.filter = function(selector) {
  2760. return DomQuery.filter(selector);
  2761. };
  2762. DomQuery.fn.is = function(selector) {
  2763. return !!selector && this.filter(selector).length > 0;
  2764. };
  2765. DomQuery.fn.init.prototype = DomQuery.fn;
  2766. return DomQuery;
  2767. });
  2768. // Included from: js/tinymce/classes/html/Styles.js
  2769. /**
  2770. * Styles.js
  2771. *
  2772. * Copyright, Moxiecode Systems AB
  2773. * Released under LGPL License.
  2774. *
  2775. * License: http://www.tinymce.com/license
  2776. * Contributing: http://www.tinymce.com/contributing
  2777. */
  2778. /**
  2779. * This class is used to parse CSS styles it also compresses styles to reduce the output size.
  2780. *
  2781. * @example
  2782. * var Styles = new tinymce.html.Styles({
  2783. * url_converter: function(url) {
  2784. * return url;
  2785. * }
  2786. * });
  2787. *
  2788. * styles = Styles.parse('border: 1px solid red');
  2789. * styles.color = 'red';
  2790. *
  2791. * console.log(new tinymce.html.StyleSerializer().serialize(styles));
  2792. *
  2793. * @class tinymce.html.Styles
  2794. * @version 3.4
  2795. */
  2796. define("tinymce/html/Styles", [], function() {
  2797. return function(settings, schema) {
  2798. /*jshint maxlen:255 */
  2799. /*eslint max-len:0 */
  2800. var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
  2801. urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
  2802. styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
  2803. trimRightRegExp = /\s+$/,
  2804. undef, i, encodingLookup = {}, encodingItems, invisibleChar = '\uFEFF';
  2805. settings = settings || {};
  2806. encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
  2807. for (i = 0; i < encodingItems.length; i++) {
  2808. encodingLookup[encodingItems[i]] = invisibleChar + i;
  2809. encodingLookup[invisibleChar + i] = encodingItems[i];
  2810. }
  2811. function toHex(match, r, g, b) {
  2812. function hex(val) {
  2813. val = parseInt(val, 10).toString(16);
  2814. return val.length > 1 ? val : '0' + val; // 0 -> 00
  2815. }
  2816. return '#' + hex(r) + hex(g) + hex(b);
  2817. }
  2818. return {
  2819. /**
  2820. * Parses the specified RGB color value and returns a hex version of that color.
  2821. *
  2822. * @method toHex
  2823. * @param {String} color RGB string value like rgb(1,2,3)
  2824. * @return {String} Hex version of that RGB value like #FF00FF.
  2825. */
  2826. toHex: function(color) {
  2827. return color.replace(rgbRegExp, toHex);
  2828. },
  2829. /**
  2830. * Parses the specified style value into an object collection. This parser will also
  2831. * merge and remove any redundant items that browsers might have added. It will also convert non hex
  2832. * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  2833. *
  2834. * @method parse
  2835. * @param {String} css Style value to parse for example: border:1px solid red;.
  2836. * @return {Object} Object representation of that style like {border: '1px solid red'}
  2837. */
  2838. parse: function(css) {
  2839. var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
  2840. var urlConverterScope = settings.url_converter_scope || this;
  2841. function compress(prefix, suffix, noJoin) {
  2842. var top, right, bottom, left;
  2843. top = styles[prefix + '-top' + suffix];
  2844. if (!top) {
  2845. return;
  2846. }
  2847. right = styles[prefix + '-right' + suffix];
  2848. if (!right) {
  2849. return;
  2850. }
  2851. bottom = styles[prefix + '-bottom' + suffix];
  2852. if (!bottom) {
  2853. return;
  2854. }
  2855. left = styles[prefix + '-left' + suffix];
  2856. if (!left) {
  2857. return;
  2858. }
  2859. var box = [top, right, bottom, left];
  2860. i = box.length - 1;
  2861. while (i--) {
  2862. if (box[i] !== box[i + 1]) {
  2863. break;
  2864. }
  2865. }
  2866. if (i > -1 && noJoin) {
  2867. return;
  2868. }
  2869. styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
  2870. delete styles[prefix + '-top' + suffix];
  2871. delete styles[prefix + '-right' + suffix];
  2872. delete styles[prefix + '-bottom' + suffix];
  2873. delete styles[prefix + '-left' + suffix];
  2874. }
  2875. /**
  2876. * Checks if the specific style can be compressed in other words if all border-width are equal.
  2877. */
  2878. function canCompress(key) {
  2879. var value = styles[key], i;
  2880. if (!value) {
  2881. return;
  2882. }
  2883. value = value.split(' ');
  2884. i = value.length;
  2885. while (i--) {
  2886. if (value[i] !== value[0]) {
  2887. return false;
  2888. }
  2889. }
  2890. styles[key] = value[0];
  2891. return true;
  2892. }
  2893. /**
  2894. * Compresses multiple styles into one style.
  2895. */
  2896. function compress2(target, a, b, c) {
  2897. if (!canCompress(a)) {
  2898. return;
  2899. }
  2900. if (!canCompress(b)) {
  2901. return;
  2902. }
  2903. if (!canCompress(c)) {
  2904. return;
  2905. }
  2906. // Compress
  2907. styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
  2908. delete styles[a];
  2909. delete styles[b];
  2910. delete styles[c];
  2911. }
  2912. // Encodes the specified string by replacing all \" \' ; : with _<num>
  2913. function encode(str) {
  2914. isEncoded = true;
  2915. return encodingLookup[str];
  2916. }
  2917. // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
  2918. // It will also decode the \" \' if keep_slashes is set to fale or omitted
  2919. function decode(str, keep_slashes) {
  2920. if (isEncoded) {
  2921. str = str.replace(/\uFEFF[0-9]/g, function(str) {
  2922. return encodingLookup[str];
  2923. });
  2924. }
  2925. if (!keep_slashes) {
  2926. str = str.replace(/\\([\'\";:])/g, "$1");
  2927. }
  2928. return str;
  2929. }
  2930. function processUrl(match, url, url2, url3, str, str2) {
  2931. str = str || str2;
  2932. if (str) {
  2933. str = decode(str);
  2934. // Force strings into single quote format
  2935. return "'" + str.replace(/\'/g, "\\'") + "'";
  2936. }
  2937. url = decode(url || url2 || url3);
  2938. if (!settings.allow_script_urls) {
  2939. var scriptUrl = url.replace(/[\s\r\n]+/, '');
  2940. if (/(java|vb)script:/i.test(scriptUrl)) {
  2941. return "";
  2942. }
  2943. if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
  2944. return "";
  2945. }
  2946. }
  2947. // Convert the URL to relative/absolute depending on config
  2948. if (urlConverter) {
  2949. url = urlConverter.call(urlConverterScope, url, 'style');
  2950. }
  2951. // Output new URL format
  2952. return "url('" + url.replace(/\'/g, "\\'") + "')";
  2953. }
  2954. if (css) {
  2955. css = css.replace(/[\u0000-\u001F]/g, '');
  2956. // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
  2957. css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
  2958. return str.replace(/[;:]/g, encode);
  2959. });
  2960. // Parse styles
  2961. while ((matches = styleRegExp.exec(css))) {
  2962. name = matches[1].replace(trimRightRegExp, '').toLowerCase();
  2963. value = matches[2].replace(trimRightRegExp, '');
  2964. // Decode escaped sequences like \65 -> e
  2965. /*jshint loopfunc:true*/
  2966. /*eslint no-loop-func:0 */
  2967. value = value.replace(/\\[0-9a-f]+/g, function(e) {
  2968. return String.fromCharCode(parseInt(e.substr(1), 16));
  2969. });
  2970. if (name && value.length > 0) {
  2971. // Don't allow behavior name or expression/comments within the values
  2972. if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
  2973. continue;
  2974. }
  2975. // Opera will produce 700 instead of bold in their style values
  2976. if (name === 'font-weight' && value === '700') {
  2977. value = 'bold';
  2978. } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
  2979. value = value.toLowerCase();
  2980. }
  2981. // Convert RGB colors to HEX
  2982. value = value.replace(rgbRegExp, toHex);
  2983. // Convert URLs and force them into url('value') format
  2984. value = value.replace(urlOrStrRegExp, processUrl);
  2985. styles[name] = isEncoded ? decode(value, true) : value;
  2986. }
  2987. styleRegExp.lastIndex = matches.index + matches[0].length;
  2988. }
  2989. // Compress the styles to reduce it's size for example IE will expand styles
  2990. compress("border", "", true);
  2991. compress("border", "-width");
  2992. compress("border", "-color");
  2993. compress("border", "-style");
  2994. compress("padding", "");
  2995. compress("margin", "");
  2996. compress2('border', 'border-width', 'border-style', 'border-color');
  2997. // Remove pointless border, IE produces these
  2998. if (styles.border === 'medium none') {
  2999. delete styles.border;
  3000. }
  3001. // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
  3002. // So lets asume it shouldn't be there
  3003. if (styles['border-image'] === 'none') {
  3004. delete styles['border-image'];
  3005. }
  3006. }
  3007. return styles;
  3008. },
  3009. /**
  3010. * Serializes the specified style object into a string.
  3011. *
  3012. * @method serialize
  3013. * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
  3014. * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
  3015. * @return {String} String representation of the style object for example: border: 1px solid red.
  3016. */
  3017. serialize: function(styles, element_name) {
  3018. var css = '', name, value;
  3019. function serializeStyles(name) {
  3020. var styleList, i, l, value;
  3021. styleList = schema.styles[name];
  3022. if (styleList) {
  3023. for (i = 0, l = styleList.length; i < l; i++) {
  3024. name = styleList[i];
  3025. value = styles[name];
  3026. if (value !== undef && value.length > 0) {
  3027. css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  3028. }
  3029. }
  3030. }
  3031. }
  3032. // Serialize styles according to schema
  3033. if (element_name && schema && schema.styles) {
  3034. // Serialize global styles and element specific styles
  3035. serializeStyles('*');
  3036. serializeStyles(element_name);
  3037. } else {
  3038. // Output the styles in the order they are inside the object
  3039. for (name in styles) {
  3040. value = styles[name];
  3041. if (value !== undef && value.length > 0) {
  3042. css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  3043. }
  3044. }
  3045. }
  3046. return css;
  3047. }
  3048. };
  3049. };
  3050. });
  3051. // Included from: js/tinymce/classes/dom/TreeWalker.js
  3052. /**
  3053. * TreeWalker.js
  3054. *
  3055. * Copyright, Moxiecode Systems AB
  3056. * Released under LGPL License.
  3057. *
  3058. * License: http://www.tinymce.com/license
  3059. * Contributing: http://www.tinymce.com/contributing
  3060. */
  3061. /**
  3062. * TreeWalker class enables you to walk the DOM in a linear manner.
  3063. *
  3064. * @class tinymce.dom.TreeWalker
  3065. */
  3066. define("tinymce/dom/TreeWalker", [], function() {
  3067. return function(start_node, root_node) {
  3068. var node = start_node;
  3069. function findSibling(node, start_name, sibling_name, shallow) {
  3070. var sibling, parent;
  3071. if (node) {
  3072. // Walk into nodes if it has a start
  3073. if (!shallow && node[start_name]) {
  3074. return node[start_name];
  3075. }
  3076. // Return the sibling if it has one
  3077. if (node != root_node) {
  3078. sibling = node[sibling_name];
  3079. if (sibling) {
  3080. return sibling;
  3081. }
  3082. // Walk up the parents to look for siblings
  3083. for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
  3084. sibling = parent[sibling_name];
  3085. if (sibling) {
  3086. return sibling;
  3087. }
  3088. }
  3089. }
  3090. }
  3091. }
  3092. /**
  3093. * Returns the current node.
  3094. *
  3095. * @method current
  3096. * @return {Node} Current node where the walker is.
  3097. */
  3098. this.current = function() {
  3099. return node;
  3100. };
  3101. /**
  3102. * Walks to the next node in tree.
  3103. *
  3104. * @method next
  3105. * @return {Node} Current node where the walker is after moving to the next node.
  3106. */
  3107. this.next = function(shallow) {
  3108. node = findSibling(node, 'firstChild', 'nextSibling', shallow);
  3109. return node;
  3110. };
  3111. /**
  3112. * Walks to the previous node in tree.
  3113. *
  3114. * @method prev
  3115. * @return {Node} Current node where the walker is after moving to the previous node.
  3116. */
  3117. this.prev = function(shallow) {
  3118. node = findSibling(node, 'lastChild', 'previousSibling', shallow);
  3119. return node;
  3120. };
  3121. };
  3122. });
  3123. // Included from: js/tinymce/classes/util/Tools.js
  3124. /**
  3125. * Tools.js
  3126. *
  3127. * Copyright, Moxiecode Systems AB
  3128. * Released under LGPL License.
  3129. *
  3130. * License: http://www.tinymce.com/license
  3131. * Contributing: http://www.tinymce.com/contributing
  3132. */
  3133. /**
  3134. * This class contains various utlity functions. These are also exposed
  3135. * directly on the tinymce namespace.
  3136. *
  3137. * @class tinymce.util.Tools
  3138. */
  3139. define("tinymce/util/Tools", [], function() {
  3140. /**
  3141. * Removes whitespace from the beginning and end of a string.
  3142. *
  3143. * @method trim
  3144. * @param {String} s String to remove whitespace from.
  3145. * @return {String} New string with removed whitespace.
  3146. */
  3147. var whiteSpaceRegExp = /^\s*|\s*$/g;
  3148. function trim(str) {
  3149. return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  3150. }
  3151. /**
  3152. * Returns true/false if the object is an array or not.
  3153. *
  3154. * @method isArray
  3155. * @param {Object} obj Object to check.
  3156. * @return {boolean} true/false state if the object is an array or not.
  3157. */
  3158. var isArray = Array.isArray || function(obj) {
  3159. return Object.prototype.toString.call(obj) === "[object Array]";
  3160. };
  3161. /**
  3162. * Checks if a object is of a specific type for example an array.
  3163. *
  3164. * @method is
  3165. * @param {Object} o Object to check type of.
  3166. * @param {string} t Optional type to check for.
  3167. * @return {Boolean} true/false if the object is of the specified type.
  3168. */
  3169. function is(o, t) {
  3170. if (!t) {
  3171. return o !== undefined;
  3172. }
  3173. if (t == 'array' && isArray(o)) {
  3174. return true;
  3175. }
  3176. return typeof(o) == t;
  3177. }
  3178. /**
  3179. * Converts the specified object into a real JavaScript array.
  3180. *
  3181. * @method toArray
  3182. * @param {Object} obj Object to convert into array.
  3183. * @return {Array} Array object based in input.
  3184. */
  3185. function toArray(obj) {
  3186. var array = [], i, l;
  3187. for (i = 0, l = obj.length; i < l; i++) {
  3188. array[i] = obj[i];
  3189. }
  3190. return array;
  3191. }
  3192. /**
  3193. * Makes a name/object map out of an array with names.
  3194. *
  3195. * @method makeMap
  3196. * @param {Array/String} items Items to make map out of.
  3197. * @param {String} delim Optional delimiter to split string by.
  3198. * @param {Object} map Optional map to add items to.
  3199. * @return {Object} Name/value map of items.
  3200. */
  3201. function makeMap(items, delim, map) {
  3202. var i;
  3203. items = items || [];
  3204. delim = delim || ',';
  3205. if (typeof(items) == "string") {
  3206. items = items.split(delim);
  3207. }
  3208. map = map || {};
  3209. i = items.length;
  3210. while (i--) {
  3211. map[items[i]] = {};
  3212. }
  3213. return map;
  3214. }
  3215. /**
  3216. * Performs an iteration of all items in a collection such as an object or array. This method will execure the
  3217. * callback function for each item in the collection, if the callback returns false the iteration will terminate.
  3218. * The callback has the following format: cb(value, key_or_index).
  3219. *
  3220. * @method each
  3221. * @param {Object} o Collection to iterate.
  3222. * @param {function} cb Callback function to execute for each item.
  3223. * @param {Object} s Optional scope to execute the callback in.
  3224. * @example
  3225. * // Iterate an array
  3226. * tinymce.each([1,2,3], function(v, i) {
  3227. * console.debug("Value: " + v + ", Index: " + i);
  3228. * });
  3229. *
  3230. * // Iterate an object
  3231. * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
  3232. * console.debug("Value: " + v + ", Key: " + k);
  3233. * });
  3234. */
  3235. function each(o, cb, s) {
  3236. var n, l;
  3237. if (!o) {
  3238. return 0;
  3239. }
  3240. s = s || o;
  3241. if (o.length !== undefined) {
  3242. // Indexed arrays, needed for Safari
  3243. for (n = 0, l = o.length; n < l; n++) {
  3244. if (cb.call(s, o[n], n, o) === false) {
  3245. return 0;
  3246. }
  3247. }
  3248. } else {
  3249. // Hashtables
  3250. for (n in o) {
  3251. if (o.hasOwnProperty(n)) {
  3252. if (cb.call(s, o[n], n, o) === false) {
  3253. return 0;
  3254. }
  3255. }
  3256. }
  3257. }
  3258. return 1;
  3259. }
  3260. /**
  3261. * Creates a new array by the return value of each iteration function call. This enables you to convert
  3262. * one array list into another.
  3263. *
  3264. * @method map
  3265. * @param {Array} a Array of items to iterate.
  3266. * @param {function} f Function to call for each item. It's return value will be the new value.
  3267. * @return {Array} Array with new values based on function return values.
  3268. */
  3269. function map(a, f) {
  3270. var o = [];
  3271. each(a, function(v) {
  3272. o.push(f(v));
  3273. });
  3274. return o;
  3275. }
  3276. /**
  3277. * Filters out items from the input array by calling the specified function for each item.
  3278. * If the function returns false the item will be excluded if it returns true it will be included.
  3279. *
  3280. * @method grep
  3281. * @param {Array} a Array of items to loop though.
  3282. * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
  3283. * @return {Array} New array with values imported and filtered based in input.
  3284. * @example
  3285. * // Filter out some items, this will return an array with 4 and 5
  3286. * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
  3287. */
  3288. function grep(a, f) {
  3289. var o = [];
  3290. each(a, function(v) {
  3291. if (!f || f(v)) {
  3292. o.push(v);
  3293. }
  3294. });
  3295. return o;
  3296. }
  3297. /**
  3298. * Creates a class, subclass or static singleton.
  3299. * More details on this method can be found in the Wiki.
  3300. *
  3301. * @method create
  3302. * @param {String} s Class name, inheritage and prefix.
  3303. * @param {Object} p Collection of methods to add to the class.
  3304. * @param {Object} root Optional root object defaults to the global window object.
  3305. * @example
  3306. * // Creates a basic class
  3307. * tinymce.create('tinymce.somepackage.SomeClass', {
  3308. * SomeClass: function() {
  3309. * // Class constructor
  3310. * },
  3311. *
  3312. * method: function() {
  3313. * // Some method
  3314. * }
  3315. * });
  3316. *
  3317. * // Creates a basic subclass class
  3318. * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
  3319. * SomeSubClass: function() {
  3320. * // Class constructor
  3321. * this.parent(); // Call parent constructor
  3322. * },
  3323. *
  3324. * method: function() {
  3325. * // Some method
  3326. * this.parent(); // Call parent method
  3327. * },
  3328. *
  3329. * 'static': {
  3330. * staticMethod: function() {
  3331. * // Static method
  3332. * }
  3333. * }
  3334. * });
  3335. *
  3336. * // Creates a singleton/static class
  3337. * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
  3338. * method: function() {
  3339. * // Some method
  3340. * }
  3341. * });
  3342. */
  3343. function create(s, p, root) {
  3344. var self = this, sp, ns, cn, scn, c, de = 0;
  3345. // Parse : <prefix> <class>:<super class>
  3346. s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
  3347. cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
  3348. // Create namespace for new class
  3349. ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
  3350. // Class already exists
  3351. if (ns[cn]) {
  3352. return;
  3353. }
  3354. // Make pure static class
  3355. if (s[2] == 'static') {
  3356. ns[cn] = p;
  3357. if (this.onCreate) {
  3358. this.onCreate(s[2], s[3], ns[cn]);
  3359. }
  3360. return;
  3361. }
  3362. // Create default constructor
  3363. if (!p[cn]) {
  3364. p[cn] = function() {};
  3365. de = 1;
  3366. }
  3367. // Add constructor and methods
  3368. ns[cn] = p[cn];
  3369. self.extend(ns[cn].prototype, p);
  3370. // Extend
  3371. if (s[5]) {
  3372. sp = self.resolve(s[5]).prototype;
  3373. scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
  3374. // Extend constructor
  3375. c = ns[cn];
  3376. if (de) {
  3377. // Add passthrough constructor
  3378. ns[cn] = function() {
  3379. return sp[scn].apply(this, arguments);
  3380. };
  3381. } else {
  3382. // Add inherit constructor
  3383. ns[cn] = function() {
  3384. this.parent = sp[scn];
  3385. return c.apply(this, arguments);
  3386. };
  3387. }
  3388. ns[cn].prototype[cn] = ns[cn];
  3389. // Add super methods
  3390. self.each(sp, function(f, n) {
  3391. ns[cn].prototype[n] = sp[n];
  3392. });
  3393. // Add overridden methods
  3394. self.each(p, function(f, n) {
  3395. // Extend methods if needed
  3396. if (sp[n]) {
  3397. ns[cn].prototype[n] = function() {
  3398. this.parent = sp[n];
  3399. return f.apply(this, arguments);
  3400. };
  3401. } else {
  3402. if (n != cn) {
  3403. ns[cn].prototype[n] = f;
  3404. }
  3405. }
  3406. });
  3407. }
  3408. // Add static methods
  3409. /*jshint sub:true*/
  3410. self.each(p['static'], function(f, n) {
  3411. ns[cn][n] = f;
  3412. });
  3413. }
  3414. /**
  3415. * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
  3416. *
  3417. * @method inArray
  3418. * @param {Array} a Array/Object to search for value in.
  3419. * @param {Object} v Value to check for inside the array.
  3420. * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
  3421. * @example
  3422. * // Get index of value in array this will alert 1 since 2 is at that index
  3423. * alert(tinymce.inArray([1,2,3], 2));
  3424. */
  3425. function inArray(a, v) {
  3426. var i, l;
  3427. if (a) {
  3428. for (i = 0, l = a.length; i < l; i++) {
  3429. if (a[i] === v) {
  3430. return i;
  3431. }
  3432. }
  3433. }
  3434. return -1;
  3435. }
  3436. function extend(obj, ext) {
  3437. var i, l, name, args = arguments, value;
  3438. for (i = 1, l = args.length; i < l; i++) {
  3439. ext = args[i];
  3440. for (name in ext) {
  3441. if (ext.hasOwnProperty(name)) {
  3442. value = ext[name];
  3443. if (value !== undefined) {
  3444. obj[name] = value;
  3445. }
  3446. }
  3447. }
  3448. }
  3449. return obj;
  3450. }
  3451. /**
  3452. * Executed the specified function for each item in a object tree.
  3453. *
  3454. * @method walk
  3455. * @param {Object} o Object tree to walk though.
  3456. * @param {function} f Function to call for each item.
  3457. * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
  3458. * @param {String} s Optional scope to execute the function in.
  3459. */
  3460. function walk(o, f, n, s) {
  3461. s = s || this;
  3462. if (o) {
  3463. if (n) {
  3464. o = o[n];
  3465. }
  3466. each(o, function(o, i) {
  3467. if (f.call(s, o, i, n) === false) {
  3468. return false;
  3469. }
  3470. walk(o, f, n, s);
  3471. });
  3472. }
  3473. }
  3474. /**
  3475. * Creates a namespace on a specific object.
  3476. *
  3477. * @method createNS
  3478. * @param {String} n Namespace to create for example a.b.c.d.
  3479. * @param {Object} o Optional object to add namespace to, defaults to window.
  3480. * @return {Object} New namespace object the last item in path.
  3481. * @example
  3482. * // Create some namespace
  3483. * tinymce.createNS('tinymce.somepackage.subpackage');
  3484. *
  3485. * // Add a singleton
  3486. * var tinymce.somepackage.subpackage.SomeSingleton = {
  3487. * method: function() {
  3488. * // Some method
  3489. * }
  3490. * };
  3491. */
  3492. function createNS(n, o) {
  3493. var i, v;
  3494. o = o || window;
  3495. n = n.split('.');
  3496. for (i = 0; i < n.length; i++) {
  3497. v = n[i];
  3498. if (!o[v]) {
  3499. o[v] = {};
  3500. }
  3501. o = o[v];
  3502. }
  3503. return o;
  3504. }
  3505. /**
  3506. * Resolves a string and returns the object from a specific structure.
  3507. *
  3508. * @method resolve
  3509. * @param {String} n Path to resolve for example a.b.c.d.
  3510. * @param {Object} o Optional object to search though, defaults to window.
  3511. * @return {Object} Last object in path or null if it couldn't be resolved.
  3512. * @example
  3513. * // Resolve a path into an object reference
  3514. * var obj = tinymce.resolve('a.b.c.d');
  3515. */
  3516. function resolve(n, o) {
  3517. var i, l;
  3518. o = o || window;
  3519. n = n.split('.');
  3520. for (i = 0, l = n.length; i < l; i++) {
  3521. o = o[n[i]];
  3522. if (!o) {
  3523. break;
  3524. }
  3525. }
  3526. return o;
  3527. }
  3528. /**
  3529. * Splits a string but removes the whitespace before and after each value.
  3530. *
  3531. * @method explode
  3532. * @param {string} s String to split.
  3533. * @param {string} d Delimiter to split by.
  3534. * @example
  3535. * // Split a string into an array with a,b,c
  3536. * var arr = tinymce.explode('a, b, c');
  3537. */
  3538. function explode(s, d) {
  3539. if (!s || is(s, 'array')) {
  3540. return s;
  3541. }
  3542. return map(s.split(d || ','), trim);
  3543. }
  3544. return {
  3545. trim: trim,
  3546. isArray: isArray,
  3547. is: is,
  3548. toArray: toArray,
  3549. makeMap: makeMap,
  3550. each: each,
  3551. map: map,
  3552. grep: grep,
  3553. inArray: inArray,
  3554. extend: extend,
  3555. create: create,
  3556. walk: walk,
  3557. createNS: createNS,
  3558. resolve: resolve,
  3559. explode: explode
  3560. };
  3561. });
  3562. // Included from: js/tinymce/classes/dom/Range.js
  3563. /**
  3564. * Range.js
  3565. *
  3566. * Copyright, Moxiecode Systems AB
  3567. * Released under LGPL License.
  3568. *
  3569. * License: http://www.tinymce.com/license
  3570. * Contributing: http://www.tinymce.com/contributing
  3571. */
  3572. define("tinymce/dom/Range", [
  3573. "tinymce/util/Tools"
  3574. ], function(Tools) {
  3575. // Range constructor
  3576. function Range(dom) {
  3577. var self = this,
  3578. doc = dom.doc,
  3579. EXTRACT = 0,
  3580. CLONE = 1,
  3581. DELETE = 2,
  3582. TRUE = true,
  3583. FALSE = false,
  3584. START_OFFSET = 'startOffset',
  3585. START_CONTAINER = 'startContainer',
  3586. END_CONTAINER = 'endContainer',
  3587. END_OFFSET = 'endOffset',
  3588. extend = Tools.extend,
  3589. nodeIndex = dom.nodeIndex;
  3590. function createDocumentFragment() {
  3591. return doc.createDocumentFragment();
  3592. }
  3593. function setStart(n, o) {
  3594. _setEndPoint(TRUE, n, o);
  3595. }
  3596. function setEnd(n, o) {
  3597. _setEndPoint(FALSE, n, o);
  3598. }
  3599. function setStartBefore(n) {
  3600. setStart(n.parentNode, nodeIndex(n));
  3601. }
  3602. function setStartAfter(n) {
  3603. setStart(n.parentNode, nodeIndex(n) + 1);
  3604. }
  3605. function setEndBefore(n) {
  3606. setEnd(n.parentNode, nodeIndex(n));
  3607. }
  3608. function setEndAfter(n) {
  3609. setEnd(n.parentNode, nodeIndex(n) + 1);
  3610. }
  3611. function collapse(ts) {
  3612. if (ts) {
  3613. self[END_CONTAINER] = self[START_CONTAINER];
  3614. self[END_OFFSET] = self[START_OFFSET];
  3615. } else {
  3616. self[START_CONTAINER] = self[END_CONTAINER];
  3617. self[START_OFFSET] = self[END_OFFSET];
  3618. }
  3619. self.collapsed = TRUE;
  3620. }
  3621. function selectNode(n) {
  3622. setStartBefore(n);
  3623. setEndAfter(n);
  3624. }
  3625. function selectNodeContents(n) {
  3626. setStart(n, 0);
  3627. setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
  3628. }
  3629. function compareBoundaryPoints(h, r) {
  3630. var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
  3631. rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
  3632. // Check START_TO_START
  3633. if (h === 0) {
  3634. return _compareBoundaryPoints(sc, so, rsc, rso);
  3635. }
  3636. // Check START_TO_END
  3637. if (h === 1) {
  3638. return _compareBoundaryPoints(ec, eo, rsc, rso);
  3639. }
  3640. // Check END_TO_END
  3641. if (h === 2) {
  3642. return _compareBoundaryPoints(ec, eo, rec, reo);
  3643. }
  3644. // Check END_TO_START
  3645. if (h === 3) {
  3646. return _compareBoundaryPoints(sc, so, rec, reo);
  3647. }
  3648. }
  3649. function deleteContents() {
  3650. _traverse(DELETE);
  3651. }
  3652. function extractContents() {
  3653. return _traverse(EXTRACT);
  3654. }
  3655. function cloneContents() {
  3656. return _traverse(CLONE);
  3657. }
  3658. function insertNode(n) {
  3659. var startContainer = this[START_CONTAINER],
  3660. startOffset = this[START_OFFSET], nn, o;
  3661. // Node is TEXT_NODE or CDATA
  3662. if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
  3663. if (!startOffset) {
  3664. // At the start of text
  3665. startContainer.parentNode.insertBefore(n, startContainer);
  3666. } else if (startOffset >= startContainer.nodeValue.length) {
  3667. // At the end of text
  3668. dom.insertAfter(n, startContainer);
  3669. } else {
  3670. // Middle, need to split
  3671. nn = startContainer.splitText(startOffset);
  3672. startContainer.parentNode.insertBefore(n, nn);
  3673. }
  3674. } else {
  3675. // Insert element node
  3676. if (startContainer.childNodes.length > 0) {
  3677. o = startContainer.childNodes[startOffset];
  3678. }
  3679. if (o) {
  3680. startContainer.insertBefore(n, o);
  3681. } else {
  3682. if (startContainer.nodeType == 3) {
  3683. dom.insertAfter(n, startContainer);
  3684. } else {
  3685. startContainer.appendChild(n);
  3686. }
  3687. }
  3688. }
  3689. }
  3690. function surroundContents(n) {
  3691. var f = self.extractContents();
  3692. self.insertNode(n);
  3693. n.appendChild(f);
  3694. self.selectNode(n);
  3695. }
  3696. function cloneRange() {
  3697. return extend(new Range(dom), {
  3698. startContainer: self[START_CONTAINER],
  3699. startOffset: self[START_OFFSET],
  3700. endContainer: self[END_CONTAINER],
  3701. endOffset: self[END_OFFSET],
  3702. collapsed: self.collapsed,
  3703. commonAncestorContainer: self.commonAncestorContainer
  3704. });
  3705. }
  3706. // Private methods
  3707. function _getSelectedNode(container, offset) {
  3708. var child;
  3709. if (container.nodeType == 3 /* TEXT_NODE */) {
  3710. return container;
  3711. }
  3712. if (offset < 0) {
  3713. return container;
  3714. }
  3715. child = container.firstChild;
  3716. while (child && offset > 0) {
  3717. --offset;
  3718. child = child.nextSibling;
  3719. }
  3720. if (child) {
  3721. return child;
  3722. }
  3723. return container;
  3724. }
  3725. function _isCollapsed() {
  3726. return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
  3727. }
  3728. function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
  3729. var c, offsetC, n, cmnRoot, childA, childB;
  3730. // In the first case the boundary-points have the same container. A is before B
  3731. // if its offset is less than the offset of B, A is equal to B if its offset is
  3732. // equal to the offset of B, and A is after B if its offset is greater than the
  3733. // offset of B.
  3734. if (containerA == containerB) {
  3735. if (offsetA == offsetB) {
  3736. return 0; // equal
  3737. }
  3738. if (offsetA < offsetB) {
  3739. return -1; // before
  3740. }
  3741. return 1; // after
  3742. }
  3743. // In the second case a child node C of the container of A is an ancestor
  3744. // container of B. In this case, A is before B if the offset of A is less than or
  3745. // equal to the index of the child node C and A is after B otherwise.
  3746. c = containerB;
  3747. while (c && c.parentNode != containerA) {
  3748. c = c.parentNode;
  3749. }
  3750. if (c) {
  3751. offsetC = 0;
  3752. n = containerA.firstChild;
  3753. while (n != c && offsetC < offsetA) {
  3754. offsetC++;
  3755. n = n.nextSibling;
  3756. }
  3757. if (offsetA <= offsetC) {
  3758. return -1; // before
  3759. }
  3760. return 1; // after
  3761. }
  3762. // In the third case a child node C of the container of B is an ancestor container
  3763. // of A. In this case, A is before B if the index of the child node C is less than
  3764. // the offset of B and A is after B otherwise.
  3765. c = containerA;
  3766. while (c && c.parentNode != containerB) {
  3767. c = c.parentNode;
  3768. }
  3769. if (c) {
  3770. offsetC = 0;
  3771. n = containerB.firstChild;
  3772. while (n != c && offsetC < offsetB) {
  3773. offsetC++;
  3774. n = n.nextSibling;
  3775. }
  3776. if (offsetC < offsetB) {
  3777. return -1; // before
  3778. }
  3779. return 1; // after
  3780. }
  3781. // In the fourth case, none of three other cases hold: the containers of A and B
  3782. // are siblings or descendants of sibling nodes. In this case, A is before B if
  3783. // the container of A is before the container of B in a pre-order traversal of the
  3784. // Ranges' context tree and A is after B otherwise.
  3785. cmnRoot = dom.findCommonAncestor(containerA, containerB);
  3786. childA = containerA;
  3787. while (childA && childA.parentNode != cmnRoot) {
  3788. childA = childA.parentNode;
  3789. }
  3790. if (!childA) {
  3791. childA = cmnRoot;
  3792. }
  3793. childB = containerB;
  3794. while (childB && childB.parentNode != cmnRoot) {
  3795. childB = childB.parentNode;
  3796. }
  3797. if (!childB) {
  3798. childB = cmnRoot;
  3799. }
  3800. if (childA == childB) {
  3801. return 0; // equal
  3802. }
  3803. n = cmnRoot.firstChild;
  3804. while (n) {
  3805. if (n == childA) {
  3806. return -1; // before
  3807. }
  3808. if (n == childB) {
  3809. return 1; // after
  3810. }
  3811. n = n.nextSibling;
  3812. }
  3813. }
  3814. function _setEndPoint(st, n, o) {
  3815. var ec, sc;
  3816. if (st) {
  3817. self[START_CONTAINER] = n;
  3818. self[START_OFFSET] = o;
  3819. } else {
  3820. self[END_CONTAINER] = n;
  3821. self[END_OFFSET] = o;
  3822. }
  3823. // If one boundary-point of a Range is set to have a root container
  3824. // other than the current one for the Range, the Range is collapsed to
  3825. // the new position. This enforces the restriction that both boundary-
  3826. // points of a Range must have the same root container.
  3827. ec = self[END_CONTAINER];
  3828. while (ec.parentNode) {
  3829. ec = ec.parentNode;
  3830. }
  3831. sc = self[START_CONTAINER];
  3832. while (sc.parentNode) {
  3833. sc = sc.parentNode;
  3834. }
  3835. if (sc == ec) {
  3836. // The start position of a Range is guaranteed to never be after the
  3837. // end position. To enforce this restriction, if the start is set to
  3838. // be at a position after the end, the Range is collapsed to that
  3839. // position.
  3840. if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
  3841. self.collapse(st);
  3842. }
  3843. } else {
  3844. self.collapse(st);
  3845. }
  3846. self.collapsed = _isCollapsed();
  3847. self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
  3848. }
  3849. function _traverse(how) {
  3850. var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
  3851. if (self[START_CONTAINER] == self[END_CONTAINER]) {
  3852. return _traverseSameContainer(how);
  3853. }
  3854. for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  3855. if (p == self[START_CONTAINER]) {
  3856. return _traverseCommonStartContainer(c, how);
  3857. }
  3858. ++endContainerDepth;
  3859. }
  3860. for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  3861. if (p == self[END_CONTAINER]) {
  3862. return _traverseCommonEndContainer(c, how);
  3863. }
  3864. ++startContainerDepth;
  3865. }
  3866. depthDiff = startContainerDepth - endContainerDepth;
  3867. startNode = self[START_CONTAINER];
  3868. while (depthDiff > 0) {
  3869. startNode = startNode.parentNode;
  3870. depthDiff--;
  3871. }
  3872. endNode = self[END_CONTAINER];
  3873. while (depthDiff < 0) {
  3874. endNode = endNode.parentNode;
  3875. depthDiff++;
  3876. }
  3877. // ascend the ancestor hierarchy until we have a common parent.
  3878. for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
  3879. startNode = sp;
  3880. endNode = ep;
  3881. }
  3882. return _traverseCommonAncestors(startNode, endNode, how);
  3883. }
  3884. function _traverseSameContainer(how) {
  3885. var frag, s, sub, n, cnt, sibling, xferNode, start, len;
  3886. if (how != DELETE) {
  3887. frag = createDocumentFragment();
  3888. }
  3889. // If selection is empty, just return the fragment
  3890. if (self[START_OFFSET] == self[END_OFFSET]) {
  3891. return frag;
  3892. }
  3893. // Text node needs special case handling
  3894. if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
  3895. // get the substring
  3896. s = self[START_CONTAINER].nodeValue;
  3897. sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
  3898. // set the original text node to its new value
  3899. if (how != CLONE) {
  3900. n = self[START_CONTAINER];
  3901. start = self[START_OFFSET];
  3902. len = self[END_OFFSET] - self[START_OFFSET];
  3903. if (start === 0 && len >= n.nodeValue.length - 1) {
  3904. n.parentNode.removeChild(n);
  3905. } else {
  3906. n.deleteData(start, len);
  3907. }
  3908. // Nothing is partially selected, so collapse to start point
  3909. self.collapse(TRUE);
  3910. }
  3911. if (how == DELETE) {
  3912. return;
  3913. }
  3914. if (sub.length > 0) {
  3915. frag.appendChild(doc.createTextNode(sub));
  3916. }
  3917. return frag;
  3918. }
  3919. // Copy nodes between the start/end offsets.
  3920. n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
  3921. cnt = self[END_OFFSET] - self[START_OFFSET];
  3922. while (n && cnt > 0) {
  3923. sibling = n.nextSibling;
  3924. xferNode = _traverseFullySelected(n, how);
  3925. if (frag) {
  3926. frag.appendChild(xferNode);
  3927. }
  3928. --cnt;
  3929. n = sibling;
  3930. }
  3931. // Nothing is partially selected, so collapse to start point
  3932. if (how != CLONE) {
  3933. self.collapse(TRUE);
  3934. }
  3935. return frag;
  3936. }
  3937. function _traverseCommonStartContainer(endAncestor, how) {
  3938. var frag, n, endIdx, cnt, sibling, xferNode;
  3939. if (how != DELETE) {
  3940. frag = createDocumentFragment();
  3941. }
  3942. n = _traverseRightBoundary(endAncestor, how);
  3943. if (frag) {
  3944. frag.appendChild(n);
  3945. }
  3946. endIdx = nodeIndex(endAncestor);
  3947. cnt = endIdx - self[START_OFFSET];
  3948. if (cnt <= 0) {
  3949. // Collapse to just before the endAncestor, which
  3950. // is partially selected.
  3951. if (how != CLONE) {
  3952. self.setEndBefore(endAncestor);
  3953. self.collapse(FALSE);
  3954. }
  3955. return frag;
  3956. }
  3957. n = endAncestor.previousSibling;
  3958. while (cnt > 0) {
  3959. sibling = n.previousSibling;
  3960. xferNode = _traverseFullySelected(n, how);
  3961. if (frag) {
  3962. frag.insertBefore(xferNode, frag.firstChild);
  3963. }
  3964. --cnt;
  3965. n = sibling;
  3966. }
  3967. // Collapse to just before the endAncestor, which
  3968. // is partially selected.
  3969. if (how != CLONE) {
  3970. self.setEndBefore(endAncestor);
  3971. self.collapse(FALSE);
  3972. }
  3973. return frag;
  3974. }
  3975. function _traverseCommonEndContainer(startAncestor, how) {
  3976. var frag, startIdx, n, cnt, sibling, xferNode;
  3977. if (how != DELETE) {
  3978. frag = createDocumentFragment();
  3979. }
  3980. n = _traverseLeftBoundary(startAncestor, how);
  3981. if (frag) {
  3982. frag.appendChild(n);
  3983. }
  3984. startIdx = nodeIndex(startAncestor);
  3985. ++startIdx; // Because we already traversed it
  3986. cnt = self[END_OFFSET] - startIdx;
  3987. n = startAncestor.nextSibling;
  3988. while (n && cnt > 0) {
  3989. sibling = n.nextSibling;
  3990. xferNode = _traverseFullySelected(n, how);
  3991. if (frag) {
  3992. frag.appendChild(xferNode);
  3993. }
  3994. --cnt;
  3995. n = sibling;
  3996. }
  3997. if (how != CLONE) {
  3998. self.setStartAfter(startAncestor);
  3999. self.collapse(TRUE);
  4000. }
  4001. return frag;
  4002. }
  4003. function _traverseCommonAncestors(startAncestor, endAncestor, how) {
  4004. var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
  4005. if (how != DELETE) {
  4006. frag = createDocumentFragment();
  4007. }
  4008. n = _traverseLeftBoundary(startAncestor, how);
  4009. if (frag) {
  4010. frag.appendChild(n);
  4011. }
  4012. startOffset = nodeIndex(startAncestor);
  4013. endOffset = nodeIndex(endAncestor);
  4014. ++startOffset;
  4015. cnt = endOffset - startOffset;
  4016. sibling = startAncestor.nextSibling;
  4017. while (cnt > 0) {
  4018. nextSibling = sibling.nextSibling;
  4019. n = _traverseFullySelected(sibling, how);
  4020. if (frag) {
  4021. frag.appendChild(n);
  4022. }
  4023. sibling = nextSibling;
  4024. --cnt;
  4025. }
  4026. n = _traverseRightBoundary(endAncestor, how);
  4027. if (frag) {
  4028. frag.appendChild(n);
  4029. }
  4030. if (how != CLONE) {
  4031. self.setStartAfter(startAncestor);
  4032. self.collapse(TRUE);
  4033. }
  4034. return frag;
  4035. }
  4036. function _traverseRightBoundary(root, how) {
  4037. var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
  4038. var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
  4039. if (next == root) {
  4040. return _traverseNode(next, isFullySelected, FALSE, how);
  4041. }
  4042. parent = next.parentNode;
  4043. clonedParent = _traverseNode(parent, FALSE, FALSE, how);
  4044. while (parent) {
  4045. while (next) {
  4046. prevSibling = next.previousSibling;
  4047. clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
  4048. if (how != DELETE) {
  4049. clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
  4050. }
  4051. isFullySelected = TRUE;
  4052. next = prevSibling;
  4053. }
  4054. if (parent == root) {
  4055. return clonedParent;
  4056. }
  4057. next = parent.previousSibling;
  4058. parent = parent.parentNode;
  4059. clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
  4060. if (how != DELETE) {
  4061. clonedGrandParent.appendChild(clonedParent);
  4062. }
  4063. clonedParent = clonedGrandParent;
  4064. }
  4065. }
  4066. function _traverseLeftBoundary(root, how) {
  4067. var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
  4068. var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
  4069. if (next == root) {
  4070. return _traverseNode(next, isFullySelected, TRUE, how);
  4071. }
  4072. parent = next.parentNode;
  4073. clonedParent = _traverseNode(parent, FALSE, TRUE, how);
  4074. while (parent) {
  4075. while (next) {
  4076. nextSibling = next.nextSibling;
  4077. clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
  4078. if (how != DELETE) {
  4079. clonedParent.appendChild(clonedChild);
  4080. }
  4081. isFullySelected = TRUE;
  4082. next = nextSibling;
  4083. }
  4084. if (parent == root) {
  4085. return clonedParent;
  4086. }
  4087. next = parent.nextSibling;
  4088. parent = parent.parentNode;
  4089. clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
  4090. if (how != DELETE) {
  4091. clonedGrandParent.appendChild(clonedParent);
  4092. }
  4093. clonedParent = clonedGrandParent;
  4094. }
  4095. }
  4096. function _traverseNode(n, isFullySelected, isLeft, how) {
  4097. var txtValue, newNodeValue, oldNodeValue, offset, newNode;
  4098. if (isFullySelected) {
  4099. return _traverseFullySelected(n, how);
  4100. }
  4101. if (n.nodeType == 3 /* TEXT_NODE */) {
  4102. txtValue = n.nodeValue;
  4103. if (isLeft) {
  4104. offset = self[START_OFFSET];
  4105. newNodeValue = txtValue.substring(offset);
  4106. oldNodeValue = txtValue.substring(0, offset);
  4107. } else {
  4108. offset = self[END_OFFSET];
  4109. newNodeValue = txtValue.substring(0, offset);
  4110. oldNodeValue = txtValue.substring(offset);
  4111. }
  4112. if (how != CLONE) {
  4113. n.nodeValue = oldNodeValue;
  4114. }
  4115. if (how == DELETE) {
  4116. return;
  4117. }
  4118. newNode = dom.clone(n, FALSE);
  4119. newNode.nodeValue = newNodeValue;
  4120. return newNode;
  4121. }
  4122. if (how == DELETE) {
  4123. return;
  4124. }
  4125. return dom.clone(n, FALSE);
  4126. }
  4127. function _traverseFullySelected(n, how) {
  4128. if (how != DELETE) {
  4129. return how == CLONE ? dom.clone(n, TRUE) : n;
  4130. }
  4131. n.parentNode.removeChild(n);
  4132. }
  4133. function toStringIE() {
  4134. return dom.create('body', null, cloneContents()).outerText;
  4135. }
  4136. extend(self, {
  4137. // Inital states
  4138. startContainer: doc,
  4139. startOffset: 0,
  4140. endContainer: doc,
  4141. endOffset: 0,
  4142. collapsed: TRUE,
  4143. commonAncestorContainer: doc,
  4144. // Range constants
  4145. START_TO_START: 0,
  4146. START_TO_END: 1,
  4147. END_TO_END: 2,
  4148. END_TO_START: 3,
  4149. // Public methods
  4150. setStart: setStart,
  4151. setEnd: setEnd,
  4152. setStartBefore: setStartBefore,
  4153. setStartAfter: setStartAfter,
  4154. setEndBefore: setEndBefore,
  4155. setEndAfter: setEndAfter,
  4156. collapse: collapse,
  4157. selectNode: selectNode,
  4158. selectNodeContents: selectNodeContents,
  4159. compareBoundaryPoints: compareBoundaryPoints,
  4160. deleteContents: deleteContents,
  4161. extractContents: extractContents,
  4162. cloneContents: cloneContents,
  4163. insertNode: insertNode,
  4164. surroundContents: surroundContents,
  4165. cloneRange: cloneRange,
  4166. toStringIE: toStringIE
  4167. });
  4168. return self;
  4169. }
  4170. // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
  4171. Range.prototype.toString = function() {
  4172. return this.toStringIE();
  4173. };
  4174. return Range;
  4175. });
  4176. // Included from: js/tinymce/classes/html/Entities.js
  4177. /**
  4178. * Entities.js
  4179. *
  4180. * Copyright, Moxiecode Systems AB
  4181. * Released under LGPL License.
  4182. *
  4183. * License: http://www.tinymce.com/license
  4184. * Contributing: http://www.tinymce.com/contributing
  4185. */
  4186. /*jshint bitwise:false */
  4187. /*eslint no-bitwise:0 */
  4188. /**
  4189. * Entity encoder class.
  4190. *
  4191. * @class tinymce.html.Entities
  4192. * @static
  4193. * @version 3.4
  4194. */
  4195. define("tinymce/html/Entities", [
  4196. "tinymce/util/Tools"
  4197. ], function(Tools) {
  4198. var makeMap = Tools.makeMap;
  4199. var namedEntities, baseEntities, reverseEntities,
  4200. attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  4201. textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  4202. rawCharsRegExp = /[<>&\"\']/g,
  4203. entityRegExp = /&(#x|#)?([\w]+);/g,
  4204. asciiMap = {
  4205. 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
  4206. 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
  4207. 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
  4208. 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
  4209. 156: "\u0153", 158: "\u017E", 159: "\u0178"
  4210. };
  4211. // Raw entities
  4212. baseEntities = {
  4213. '\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
  4214. "'": '&#39;',
  4215. '<': '&lt;',
  4216. '>': '&gt;',
  4217. '&': '&amp;',
  4218. '\u0060': '&#96;'
  4219. };
  4220. // Reverse lookup table for raw entities
  4221. reverseEntities = {
  4222. '&lt;': '<',
  4223. '&gt;': '>',
  4224. '&amp;': '&',
  4225. '&quot;': '"',
  4226. '&apos;': "'"
  4227. };
  4228. // Decodes text by using the browser
  4229. function nativeDecode(text) {
  4230. var elm;
  4231. elm = document.createElement("div");
  4232. elm.innerHTML = text;
  4233. return elm.textContent || elm.innerText || text;
  4234. }
  4235. // Build a two way lookup table for the entities
  4236. function buildEntitiesLookup(items, radix) {
  4237. var i, chr, entity, lookup = {};
  4238. if (items) {
  4239. items = items.split(',');
  4240. radix = radix || 10;
  4241. // Build entities lookup table
  4242. for (i = 0; i < items.length; i += 2) {
  4243. chr = String.fromCharCode(parseInt(items[i], radix));
  4244. // Only add non base entities
  4245. if (!baseEntities[chr]) {
  4246. entity = '&' + items[i + 1] + ';';
  4247. lookup[chr] = entity;
  4248. lookup[entity] = chr;
  4249. }
  4250. }
  4251. return lookup;
  4252. }
  4253. }
  4254. // Unpack entities lookup where the numbers are in radix 32 to reduce the size
  4255. namedEntities = buildEntitiesLookup(
  4256. '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
  4257. '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
  4258. '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
  4259. '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
  4260. '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
  4261. '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
  4262. '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
  4263. '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
  4264. '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
  4265. '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
  4266. 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
  4267. 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
  4268. 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
  4269. 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
  4270. 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
  4271. '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
  4272. '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
  4273. '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
  4274. '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
  4275. '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
  4276. 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
  4277. 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
  4278. 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
  4279. '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
  4280. '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
  4281. var Entities = {
  4282. /**
  4283. * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
  4284. *
  4285. * @method encodeRaw
  4286. * @param {String} text Text to encode.
  4287. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  4288. * @return {String} Entity encoded text.
  4289. */
  4290. encodeRaw: function(text, attr) {
  4291. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4292. return baseEntities[chr] || chr;
  4293. });
  4294. },
  4295. /**
  4296. * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
  4297. * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
  4298. * and is exposed as the DOMUtils.encode function.
  4299. *
  4300. * @method encodeAllRaw
  4301. * @param {String} text Text to encode.
  4302. * @return {String} Entity encoded text.
  4303. */
  4304. encodeAllRaw: function(text) {
  4305. return ('' + text).replace(rawCharsRegExp, function(chr) {
  4306. return baseEntities[chr] || chr;
  4307. });
  4308. },
  4309. /**
  4310. * Encodes the specified string using numeric entities. The core entities will be
  4311. * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
  4312. *
  4313. * @method encodeNumeric
  4314. * @param {String} text Text to encode.
  4315. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  4316. * @return {String} Entity encoded text.
  4317. */
  4318. encodeNumeric: function(text, attr) {
  4319. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4320. // Multi byte sequence convert it to a single entity
  4321. if (chr.length > 1) {
  4322. return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
  4323. }
  4324. return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
  4325. });
  4326. },
  4327. /**
  4328. * Encodes the specified string using named entities. The core entities will be encoded
  4329. * as named ones but all non lower ascii characters will be encoded into named entities.
  4330. *
  4331. * @method encodeNamed
  4332. * @param {String} text Text to encode.
  4333. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  4334. * @param {Object} entities Optional parameter with entities to use.
  4335. * @return {String} Entity encoded text.
  4336. */
  4337. encodeNamed: function(text, attr, entities) {
  4338. entities = entities || namedEntities;
  4339. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4340. return baseEntities[chr] || entities[chr] || chr;
  4341. });
  4342. },
  4343. /**
  4344. * Returns an encode function based on the name(s) and it's optional entities.
  4345. *
  4346. * @method getEncodeFunc
  4347. * @param {String} name Comma separated list of encoders for example named,numeric.
  4348. * @param {String} entities Optional parameter with entities to use instead of the built in set.
  4349. * @return {function} Encode function to be used.
  4350. */
  4351. getEncodeFunc: function(name, entities) {
  4352. entities = buildEntitiesLookup(entities) || namedEntities;
  4353. function encodeNamedAndNumeric(text, attr) {
  4354. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4355. return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
  4356. });
  4357. }
  4358. function encodeCustomNamed(text, attr) {
  4359. return Entities.encodeNamed(text, attr, entities);
  4360. }
  4361. // Replace + with , to be compatible with previous TinyMCE versions
  4362. name = makeMap(name.replace(/\+/g, ','));
  4363. // Named and numeric encoder
  4364. if (name.named && name.numeric) {
  4365. return encodeNamedAndNumeric;
  4366. }
  4367. // Named encoder
  4368. if (name.named) {
  4369. // Custom names
  4370. if (entities) {
  4371. return encodeCustomNamed;
  4372. }
  4373. return Entities.encodeNamed;
  4374. }
  4375. // Numeric
  4376. if (name.numeric) {
  4377. return Entities.encodeNumeric;
  4378. }
  4379. // Raw encoder
  4380. return Entities.encodeRaw;
  4381. },
  4382. /**
  4383. * Decodes the specified string, this will replace entities with raw UTF characters.
  4384. *
  4385. * @method decode
  4386. * @param {String} text Text to entity decode.
  4387. * @return {String} Entity decoded string.
  4388. */
  4389. decode: function(text) {
  4390. return text.replace(entityRegExp, function(all, numeric, value) {
  4391. if (numeric) {
  4392. value = parseInt(value, numeric.length === 2 ? 16 : 10);
  4393. // Support upper UTF
  4394. if (value > 0xFFFF) {
  4395. value -= 0x10000;
  4396. return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
  4397. } else {
  4398. return asciiMap[value] || String.fromCharCode(value);
  4399. }
  4400. }
  4401. return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
  4402. });
  4403. }
  4404. };
  4405. return Entities;
  4406. });
  4407. // Included from: js/tinymce/classes/Env.js
  4408. /**
  4409. * Env.js
  4410. *
  4411. * Copyright, Moxiecode Systems AB
  4412. * Released under LGPL License.
  4413. *
  4414. * License: http://www.tinymce.com/license
  4415. * Contributing: http://www.tinymce.com/contributing
  4416. */
  4417. /**
  4418. * This class contains various environment constants like browser versions etc.
  4419. * Normally you don't want to sniff specific browser versions but sometimes you have
  4420. * to when it's impossible to feature detect. So use this with care.
  4421. *
  4422. * @class tinymce.Env
  4423. * @static
  4424. */
  4425. define("tinymce/Env", [], function() {
  4426. var nav = navigator, userAgent = nav.userAgent;
  4427. var opera, webkit, ie, ie11, gecko, mac, iDevice;
  4428. opera = window.opera && window.opera.buildNumber;
  4429. webkit = /WebKit/.test(userAgent);
  4430. ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
  4431. ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
  4432. ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
  4433. ie = ie || ie11;
  4434. gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
  4435. mac = userAgent.indexOf('Mac') != -1;
  4436. iDevice = /(iPad|iPhone)/.test(userAgent);
  4437. // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
  4438. // says it has contentEditable support but there is no visible caret.
  4439. var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
  4440. return {
  4441. /**
  4442. * Constant that is true if the browser is Opera.
  4443. *
  4444. * @property opera
  4445. * @type Boolean
  4446. * @final
  4447. */
  4448. opera: opera,
  4449. /**
  4450. * Constant that is true if the browser is WebKit (Safari/Chrome).
  4451. *
  4452. * @property webKit
  4453. * @type Boolean
  4454. * @final
  4455. */
  4456. webkit: webkit,
  4457. /**
  4458. * Constant that is more than zero if the browser is IE.
  4459. *
  4460. * @property ie
  4461. * @type Boolean
  4462. * @final
  4463. */
  4464. ie: ie,
  4465. /**
  4466. * Constant that is true if the browser is Gecko.
  4467. *
  4468. * @property gecko
  4469. * @type Boolean
  4470. * @final
  4471. */
  4472. gecko: gecko,
  4473. /**
  4474. * Constant that is true if the os is Mac OS.
  4475. *
  4476. * @property mac
  4477. * @type Boolean
  4478. * @final
  4479. */
  4480. mac: mac,
  4481. /**
  4482. * Constant that is true if the os is iOS.
  4483. *
  4484. * @property iOS
  4485. * @type Boolean
  4486. * @final
  4487. */
  4488. iOS: iDevice,
  4489. /**
  4490. * Constant that is true if the browser supports editing.
  4491. *
  4492. * @property contentEditable
  4493. * @type Boolean
  4494. * @final
  4495. */
  4496. contentEditable: contentEditable,
  4497. /**
  4498. * Transparent image data url.
  4499. *
  4500. * @property transparentSrc
  4501. * @type Boolean
  4502. * @final
  4503. */
  4504. transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
  4505. /**
  4506. * Returns true/false if the browser can or can't place the caret after a inline block like an image.
  4507. *
  4508. * @property noCaretAfter
  4509. * @type Boolean
  4510. * @final
  4511. */
  4512. caretAfter: ie != 8,
  4513. /**
  4514. * Constant that is true if the browser supports native DOM Ranges. IE 9+.
  4515. *
  4516. * @property range
  4517. * @type Boolean
  4518. */
  4519. range: window.getSelection && "Range" in window,
  4520. /**
  4521. * Returns the IE document mode for non IE browsers this will fake IE 10.
  4522. *
  4523. * @property documentMode
  4524. * @type Number
  4525. */
  4526. documentMode: ie ? (document.documentMode || 7) : 10
  4527. };
  4528. });
  4529. // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
  4530. /**
  4531. * StyleSheetLoader.js
  4532. *
  4533. * Copyright, Moxiecode Systems AB
  4534. * Released under LGPL License.
  4535. *
  4536. * License: http://www.tinymce.com/license
  4537. * Contributing: http://www.tinymce.com/contributing
  4538. */
  4539. /**
  4540. * This class handles loading of external stylesheets and fires events when these are loaded.
  4541. *
  4542. * @class tinymce.dom.StyleSheetLoader
  4543. * @private
  4544. */
  4545. define("tinymce/dom/StyleSheetLoader", [], function() {
  4546. "use strict";
  4547. return function(document, settings) {
  4548. var idCount = 0, loadedStates = {}, maxLoadTime;
  4549. settings = settings || {};
  4550. maxLoadTime = settings.maxLoadTime || 5000;
  4551. function appendToHead(node) {
  4552. document.getElementsByTagName('head')[0].appendChild(node);
  4553. }
  4554. /**
  4555. * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
  4556. *
  4557. * @method load
  4558. * @param {String} url Url to be loaded.
  4559. * @param {Function} loadedCallback Callback to be executed when loaded.
  4560. * @param {Function} errorCallback Callback to be executed when failed loading.
  4561. */
  4562. function load(url, loadedCallback, errorCallback) {
  4563. var link, style, startTime, state;
  4564. function passed() {
  4565. var callbacks = state.passed, i = callbacks.length;
  4566. while (i--) {
  4567. callbacks[i]();
  4568. }
  4569. state.status = 2;
  4570. state.passed = [];
  4571. state.failed = [];
  4572. }
  4573. function failed() {
  4574. var callbacks = state.failed, i = callbacks.length;
  4575. while (i--) {
  4576. callbacks[i]();
  4577. }
  4578. state.status = 3;
  4579. state.passed = [];
  4580. state.failed = [];
  4581. }
  4582. // Sniffs for older WebKit versions that have the link.onload but a broken one
  4583. function isOldWebKit() {
  4584. var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
  4585. return !!(webKitChunks && webKitChunks[1] < 536);
  4586. }
  4587. // Calls the waitCallback until the test returns true or the timeout occurs
  4588. function wait(testCallback, waitCallback) {
  4589. if (!testCallback()) {
  4590. // Wait for timeout
  4591. if ((new Date().getTime()) - startTime < maxLoadTime) {
  4592. window.setTimeout(waitCallback, 0);
  4593. } else {
  4594. failed();
  4595. }
  4596. }
  4597. }
  4598. // Workaround for WebKit that doesn't properly support the onload event for link elements
  4599. // Or WebKit that fires the onload event before the StyleSheet is added to the document
  4600. function waitForWebKitLinkLoaded() {
  4601. wait(function() {
  4602. var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
  4603. while (i--) {
  4604. styleSheet = styleSheets[i];
  4605. owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
  4606. if (owner && owner.id === link.id) {
  4607. passed();
  4608. return true;
  4609. }
  4610. }
  4611. }, waitForWebKitLinkLoaded);
  4612. }
  4613. // Workaround for older Geckos that doesn't have any onload event for StyleSheets
  4614. function waitForGeckoLinkLoaded() {
  4615. wait(function() {
  4616. try {
  4617. // Accessing the cssRules will throw an exception until the CSS file is loaded
  4618. var cssRules = style.sheet.cssRules;
  4619. passed();
  4620. return !!cssRules;
  4621. } catch (ex) {
  4622. // Ignore
  4623. }
  4624. }, waitForGeckoLinkLoaded);
  4625. }
  4626. if (!loadedStates[url]) {
  4627. state = {
  4628. passed: [],
  4629. failed: []
  4630. };
  4631. loadedStates[url] = state;
  4632. } else {
  4633. state = loadedStates[url];
  4634. }
  4635. if (loadedCallback) {
  4636. state.passed.push(loadedCallback);
  4637. }
  4638. if (errorCallback) {
  4639. state.failed.push(errorCallback);
  4640. }
  4641. // Is loading wait for it to pass
  4642. if (state.status == 1) {
  4643. return;
  4644. }
  4645. // Has finished loading and was success
  4646. if (state.status == 2) {
  4647. passed();
  4648. return;
  4649. }
  4650. // Has finished loading and was a failure
  4651. if (state.status == 3) {
  4652. failed();
  4653. return;
  4654. }
  4655. // Start loading
  4656. state.status = 1;
  4657. link = document.createElement('link');
  4658. link.rel = 'stylesheet';
  4659. link.type = 'text/css';
  4660. link.id = 'u' + (idCount++);
  4661. link.async = false;
  4662. link.defer = false;
  4663. startTime = new Date().getTime();
  4664. // Feature detect onload on link element and sniff older webkits since it has an broken onload event
  4665. if ("onload" in link && !isOldWebKit()) {
  4666. link.onload = waitForWebKitLinkLoaded;
  4667. link.onerror = failed;
  4668. } else {
  4669. // Sniff for old Firefox that doesn't support the onload event on link elements
  4670. // TODO: Remove this in the future when everyone uses modern browsers
  4671. if (navigator.userAgent.indexOf("Firefox") > 0) {
  4672. style = document.createElement('style');
  4673. style.textContent = '@import "' + url + '"';
  4674. waitForGeckoLinkLoaded();
  4675. appendToHead(style);
  4676. return;
  4677. } else {
  4678. // Use the id owner on older webkits
  4679. waitForWebKitLinkLoaded();
  4680. }
  4681. }
  4682. appendToHead(link);
  4683. link.href = url;
  4684. }
  4685. this.load = load;
  4686. };
  4687. });
  4688. // Included from: js/tinymce/classes/dom/DOMUtils.js
  4689. /**
  4690. * DOMUtils.js
  4691. *
  4692. * Copyright, Moxiecode Systems AB
  4693. * Released under LGPL License.
  4694. *
  4695. * License: http://www.tinymce.com/license
  4696. * Contributing: http://www.tinymce.com/contributing
  4697. */
  4698. /**
  4699. * Utility class for various DOM manipulation and retrieval functions.
  4700. *
  4701. * @class tinymce.dom.DOMUtils
  4702. * @example
  4703. * // Add a class to an element by id in the page
  4704. * tinymce.DOM.addClass('someid', 'someclass');
  4705. *
  4706. * // Add a class to an element by id inside the editor
  4707. * tinymce.activeEditor.dom.addClass('someid', 'someclass');
  4708. */
  4709. define("tinymce/dom/DOMUtils", [
  4710. "tinymce/dom/Sizzle",
  4711. "tinymce/html/Styles",
  4712. "tinymce/dom/EventUtils",
  4713. "tinymce/dom/TreeWalker",
  4714. "tinymce/dom/Range",
  4715. "tinymce/html/Entities",
  4716. "tinymce/Env",
  4717. "tinymce/util/Tools",
  4718. "tinymce/dom/StyleSheetLoader"
  4719. ], function(Sizzle, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
  4720. // Shorten names
  4721. var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim, extend = Tools.extend;
  4722. var isWebKit = Env.webkit, isIE = Env.ie;
  4723. var simpleSelectorRe = /^([a-z0-9],?)+$/i;
  4724. var whiteSpaceRegExp = /^[ \t\r\n]*$/;
  4725. var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
  4726. /**
  4727. * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
  4728. *
  4729. * @constructor
  4730. * @method DOMUtils
  4731. * @param {Document} d Document reference to bind the utility class to.
  4732. * @param {settings} s Optional settings collection.
  4733. */
  4734. function DOMUtils(doc, settings) {
  4735. var self = this, blockElementsMap;
  4736. self.doc = doc;
  4737. self.win = window;
  4738. self.files = {};
  4739. self.counter = 0;
  4740. self.stdMode = !isIE || doc.documentMode >= 8;
  4741. self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
  4742. self.hasOuterHTML = "outerHTML" in doc.createElement("a");
  4743. self.styleSheetLoader = new StyleSheetLoader(doc);
  4744. this.boundEvents = [];
  4745. self.settings = settings = extend({
  4746. keep_values: false,
  4747. hex_colors: 1
  4748. }, settings);
  4749. self.schema = settings.schema;
  4750. self.styles = new Styles({
  4751. url_converter: settings.url_converter,
  4752. url_converter_scope: settings.url_converter_scope
  4753. }, settings.schema);
  4754. self.fixDoc(doc);
  4755. self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
  4756. blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
  4757. /**
  4758. * Returns true/false if the specified element is a block element or not.
  4759. *
  4760. * @method isBlock
  4761. * @param {Node/String} node Element/Node to check.
  4762. * @return {Boolean} True/False state if the node is a block element or not.
  4763. */
  4764. self.isBlock = function(node) {
  4765. // Fix for #5446
  4766. if (!node) {
  4767. return false;
  4768. }
  4769. // This function is called in module pattern style since it might be executed with the wrong this scope
  4770. var type = node.nodeType;
  4771. // If it's a node then check the type and use the nodeName
  4772. if (type) {
  4773. return !!(type === 1 && blockElementsMap[node.nodeName]);
  4774. }
  4775. return !!blockElementsMap[node];
  4776. };
  4777. }
  4778. DOMUtils.prototype = {
  4779. root: null,
  4780. props: {
  4781. "for": "htmlFor",
  4782. "class": "className",
  4783. className: "className",
  4784. checked: "checked",
  4785. disabled: "disabled",
  4786. maxlength: "maxLength",
  4787. readonly: "readOnly",
  4788. selected: "selected",
  4789. value: "value",
  4790. id: "id",
  4791. name: "name",
  4792. type: "type"
  4793. },
  4794. fixDoc: function(doc) {
  4795. var settings = this.settings, name;
  4796. if (isIE && settings.schema) {
  4797. // Add missing HTML 4/5 elements to IE
  4798. ('abbr article aside audio canvas ' +
  4799. 'details figcaption figure footer ' +
  4800. 'header hgroup mark menu meter nav ' +
  4801. 'output progress section summary ' +
  4802. 'time video').replace(/\w+/g, function(name) {
  4803. doc.createElement(name);
  4804. });
  4805. // Create all custom elements
  4806. for (name in settings.schema.getCustomElements()) {
  4807. doc.createElement(name);
  4808. }
  4809. }
  4810. },
  4811. clone: function(node, deep) {
  4812. var self = this, clone, doc;
  4813. // TODO: Add feature detection here in the future
  4814. if (!isIE || node.nodeType !== 1 || deep) {
  4815. return node.cloneNode(deep);
  4816. }
  4817. doc = self.doc;
  4818. // Make a HTML5 safe shallow copy
  4819. if (!deep) {
  4820. clone = doc.createElement(node.nodeName);
  4821. // Copy attribs
  4822. each(self.getAttribs(node), function(attr) {
  4823. self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
  4824. });
  4825. return clone;
  4826. }
  4827. /*
  4828. // Setup HTML5 patched document fragment
  4829. if (!self.frag) {
  4830. self.frag = doc.createDocumentFragment();
  4831. self.fixDoc(self.frag);
  4832. }
  4833. // Make a deep copy by adding it to the document fragment then removing it this removed the :section
  4834. clone = doc.createElement('div');
  4835. self.frag.appendChild(clone);
  4836. clone.innerHTML = node.outerHTML;
  4837. self.frag.removeChild(clone);
  4838. */
  4839. return clone.firstChild;
  4840. },
  4841. /**
  4842. * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
  4843. * go above the point of this root node.
  4844. *
  4845. * @method getRoot
  4846. * @return {Element} Root element for the utility class.
  4847. */
  4848. getRoot: function() {
  4849. var self = this;
  4850. return self.get(self.settings.root_element) || self.doc.body;
  4851. },
  4852. /**
  4853. * Returns the viewport of the window.
  4854. *
  4855. * @method getViewPort
  4856. * @param {Window} win Optional window to get viewport of.
  4857. * @return {Object} Viewport object with fields x, y, w and h.
  4858. */
  4859. getViewPort: function(win) {
  4860. var doc, rootElm;
  4861. win = !win ? this.win : win;
  4862. doc = win.document;
  4863. rootElm = this.boxModel ? doc.documentElement : doc.body;
  4864. // Returns viewport size excluding scrollbars
  4865. return {
  4866. x: win.pageXOffset || rootElm.scrollLeft,
  4867. y: win.pageYOffset || rootElm.scrollTop,
  4868. w: win.innerWidth || rootElm.clientWidth,
  4869. h: win.innerHeight || rootElm.clientHeight
  4870. };
  4871. },
  4872. /**
  4873. * Returns the rectangle for a specific element.
  4874. *
  4875. * @method getRect
  4876. * @param {Element/String} elm Element object or element ID to get rectangle from.
  4877. * @return {object} Rectangle for specified element object with x, y, w, h fields.
  4878. */
  4879. getRect: function(elm) {
  4880. var self = this, pos, size;
  4881. elm = self.get(elm);
  4882. pos = self.getPos(elm);
  4883. size = self.getSize(elm);
  4884. return {
  4885. x: pos.x, y: pos.y,
  4886. w: size.w, h: size.h
  4887. };
  4888. },
  4889. /**
  4890. * Returns the size dimensions of the specified element.
  4891. *
  4892. * @method getSize
  4893. * @param {Element/String} elm Element object or element ID to get rectangle from.
  4894. * @return {object} Rectangle for specified element object with w, h fields.
  4895. */
  4896. getSize: function(elm) {
  4897. var self = this, w, h;
  4898. elm = self.get(elm);
  4899. w = self.getStyle(elm, 'width');
  4900. h = self.getStyle(elm, 'height');
  4901. // Non pixel value, then force offset/clientWidth
  4902. if (w.indexOf('px') === -1) {
  4903. w = 0;
  4904. }
  4905. // Non pixel value, then force offset/clientWidth
  4906. if (h.indexOf('px') === -1) {
  4907. h = 0;
  4908. }
  4909. return {
  4910. w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
  4911. h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
  4912. };
  4913. },
  4914. /**
  4915. * Returns a node by the specified selector function. This function will
  4916. * loop through all parent nodes and call the specified function for each node.
  4917. * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
  4918. * and the node it found will be returned.
  4919. *
  4920. * @method getParent
  4921. * @param {Node/String} node DOM node to search parents on or ID string.
  4922. * @param {function} selector Selection function or CSS selector to execute on each node.
  4923. * @param {Node} root Optional root element, never go below this point.
  4924. * @return {Node} DOM Node or null if it wasn't found.
  4925. */
  4926. getParent: function(node, selector, root) {
  4927. return this.getParents(node, selector, root, false);
  4928. },
  4929. /**
  4930. * Returns a node list of all parents matching the specified selector function or pattern.
  4931. * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
  4932. *
  4933. * @method getParents
  4934. * @param {Node/String} node DOM node to search parents on or ID string.
  4935. * @param {function} selector Selection function to execute on each node or CSS pattern.
  4936. * @param {Node} root Optional root element, never go below this point.
  4937. * @return {Array} Array of nodes or null if it wasn't found.
  4938. */
  4939. getParents: function(node, selector, root, collect) {
  4940. var self = this, selectorVal, result = [];
  4941. node = self.get(node);
  4942. collect = collect === undefined;
  4943. // Default root on inline mode
  4944. root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
  4945. // Wrap node name as func
  4946. if (is(selector, 'string')) {
  4947. selectorVal = selector;
  4948. if (selector === '*') {
  4949. selector = function(node) {return node.nodeType == 1;};
  4950. } else {
  4951. selector = function(node) {
  4952. return self.is(node, selectorVal);
  4953. };
  4954. }
  4955. }
  4956. while (node) {
  4957. if (node == root || !node.nodeType || node.nodeType === 9) {
  4958. break;
  4959. }
  4960. if (!selector || selector(node)) {
  4961. if (collect) {
  4962. result.push(node);
  4963. } else {
  4964. return node;
  4965. }
  4966. }
  4967. node = node.parentNode;
  4968. }
  4969. return collect ? result : null;
  4970. },
  4971. /**
  4972. * Returns the specified element by ID or the input element if it isn't a string.
  4973. *
  4974. * @method get
  4975. * @param {String/Element} n Element id to look for or element to just pass though.
  4976. * @return {Element} Element matching the specified id or null if it wasn't found.
  4977. */
  4978. get: function(elm) {
  4979. var name;
  4980. if (elm && this.doc && typeof(elm) == 'string') {
  4981. name = elm;
  4982. elm = this.doc.getElementById(elm);
  4983. // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
  4984. if (elm && elm.id !== name) {
  4985. return this.doc.getElementsByName(name)[1];
  4986. }
  4987. }
  4988. return elm;
  4989. },
  4990. /**
  4991. * Returns the next node that matches selector or function
  4992. *
  4993. * @method getNext
  4994. * @param {Node} node Node to find siblings from.
  4995. * @param {String/function} selector Selector CSS expression or function.
  4996. * @return {Node} Next node item matching the selector or null if it wasn't found.
  4997. */
  4998. getNext: function(node, selector) {
  4999. return this._findSib(node, selector, 'nextSibling');
  5000. },
  5001. /**
  5002. * Returns the previous node that matches selector or function
  5003. *
  5004. * @method getPrev
  5005. * @param {Node} node Node to find siblings from.
  5006. * @param {String/function} selector Selector CSS expression or function.
  5007. * @return {Node} Previous node item matching the selector or null if it wasn't found.
  5008. */
  5009. getPrev: function(node, selector) {
  5010. return this._findSib(node, selector, 'previousSibling');
  5011. },
  5012. // #ifndef jquery
  5013. /**
  5014. * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
  5015. * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
  5016. * on more complex patterns.
  5017. *
  5018. * @method select
  5019. * @param {String} selector CSS level 3 pattern to select/find elements by.
  5020. * @param {Object} scope Optional root element/scope element to search in.
  5021. * @return {Array} Array with all matched elements.
  5022. * @example
  5023. * // Adds a class to all paragraphs in the currently active editor
  5024. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  5025. *
  5026. * // Adds a class to all spans that have the test class in the currently active editor
  5027. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
  5028. */
  5029. select: function(selector, scope) {
  5030. var self = this;
  5031. //Sizzle.selectors.cacheLength = 0;
  5032. return Sizzle(selector, self.get(scope) || self.get(self.settings.root_element) || self.doc, []);
  5033. },
  5034. /**
  5035. * Returns true/false if the specified element matches the specified css pattern.
  5036. *
  5037. * @method is
  5038. * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
  5039. * @param {String} selector CSS pattern to match the element against.
  5040. */
  5041. is: function(elm, selector) {
  5042. var i;
  5043. // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
  5044. if (elm.length === undefined) {
  5045. // Simple all selector
  5046. if (selector === '*') {
  5047. return elm.nodeType == 1;
  5048. }
  5049. // Simple selector just elements
  5050. if (simpleSelectorRe.test(selector)) {
  5051. selector = selector.toLowerCase().split(/,/);
  5052. elm = elm.nodeName.toLowerCase();
  5053. for (i = selector.length - 1; i >= 0; i--) {
  5054. if (selector[i] == elm) {
  5055. return true;
  5056. }
  5057. }
  5058. return false;
  5059. }
  5060. }
  5061. // Is non element
  5062. if (elm.nodeType && elm.nodeType != 1) {
  5063. return false;
  5064. }
  5065. var elms = elm.nodeType ? [elm] : elm;
  5066. return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
  5067. },
  5068. // #endif
  5069. /**
  5070. * Adds the specified element to another element or elements.
  5071. *
  5072. * @method add
  5073. * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
  5074. * @param {String/Element} name Name of new element to add or existing element to add.
  5075. * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
  5076. * @param {String} html Optional inner HTML contents to add for each element.
  5077. * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
  5078. * were passed in.
  5079. * @example
  5080. * // Adds a new paragraph to the end of the active editor
  5081. * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
  5082. */
  5083. add: function(parentElm, name, attrs, html, create) {
  5084. var self = this;
  5085. return this.run(parentElm, function(parentElm) {
  5086. var newElm;
  5087. newElm = is(name, 'string') ? self.doc.createElement(name) : name;
  5088. self.setAttribs(newElm, attrs);
  5089. if (html) {
  5090. if (html.nodeType) {
  5091. newElm.appendChild(html);
  5092. } else {
  5093. self.setHTML(newElm, html);
  5094. }
  5095. }
  5096. return !create ? parentElm.appendChild(newElm) : newElm;
  5097. });
  5098. },
  5099. /**
  5100. * Creates a new element.
  5101. *
  5102. * @method create
  5103. * @param {String} name Name of new element.
  5104. * @param {Object} attrs Optional object name/value collection with element attributes.
  5105. * @param {String} html Optional HTML string to set as inner HTML of the element.
  5106. * @return {Element} HTML DOM node element that got created.
  5107. * @example
  5108. * // Adds an element where the caret/selection is in the active editor
  5109. * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
  5110. * tinymce.activeEditor.selection.setNode(el);
  5111. */
  5112. create: function(name, attrs, html) {
  5113. return this.add(this.doc.createElement(name), name, attrs, html, 1);
  5114. },
  5115. /**
  5116. * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
  5117. *
  5118. * @method createHTML
  5119. * @param {String} name Name of new element.
  5120. * @param {Object} attrs Optional object name/value collection with element attributes.
  5121. * @param {String} html Optional HTML string to set as inner HTML of the element.
  5122. * @return {String} String with new HTML element, for example: <a href="#">test</a>.
  5123. * @example
  5124. * // Creates a html chunk and inserts it at the current selection/caret location
  5125. * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
  5126. */
  5127. createHTML: function(name, attrs, html) {
  5128. var outHtml = '', key;
  5129. outHtml += '<' + name;
  5130. for (key in attrs) {
  5131. if (attrs.hasOwnProperty(key) && attrs[key] !== null) {
  5132. outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
  5133. }
  5134. }
  5135. // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
  5136. if (typeof(html) != "undefined") {
  5137. return outHtml + '>' + html + '</' + name + '>';
  5138. }
  5139. return outHtml + ' />';
  5140. },
  5141. /**
  5142. * Creates a document fragment out of the specified HTML string.
  5143. *
  5144. * @method createFragment
  5145. * @param {String} html Html string to create fragment from.
  5146. * @return {DocumentFragment} Document fragment node.
  5147. */
  5148. createFragment: function(html) {
  5149. var frag, node, doc = this.doc, container;
  5150. container = doc.createElement("div");
  5151. frag = doc.createDocumentFragment();
  5152. if (html) {
  5153. container.innerHTML = html;
  5154. }
  5155. while ((node = container.firstChild)) {
  5156. frag.appendChild(node);
  5157. }
  5158. return frag;
  5159. },
  5160. /**
  5161. * Removes/deletes the specified element(s) from the DOM.
  5162. *
  5163. * @method remove
  5164. * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
  5165. * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be
  5166. * placed at the location of the removed element.
  5167. * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
  5168. * were passed in.
  5169. * @example
  5170. * // Removes all paragraphs in the active editor
  5171. * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
  5172. *
  5173. * // Removes an element by id in the document
  5174. * tinymce.DOM.remove('mydiv');
  5175. */
  5176. remove: function(node, keep_children) {
  5177. return this.run(node, function(node) {
  5178. var child, parent = node.parentNode;
  5179. if (!parent) {
  5180. return null;
  5181. }
  5182. if (keep_children) {
  5183. while ((child = node.firstChild)) {
  5184. // IE 8 will crash if you don't remove completely empty text nodes
  5185. if (!isIE || child.nodeType !== 3 || child.nodeValue) {
  5186. parent.insertBefore(child, node);
  5187. } else {
  5188. node.removeChild(child);
  5189. }
  5190. }
  5191. }
  5192. return parent.removeChild(node);
  5193. });
  5194. },
  5195. /**
  5196. * Sets the CSS style value on a HTML element. The name can be a camelcase string
  5197. * or the CSS style name like background-color.
  5198. *
  5199. * @method setStyle
  5200. * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
  5201. * @param {String} na Name of the style value to set.
  5202. * @param {String} v Value to set on the style.
  5203. * @example
  5204. * // Sets a style value on all paragraphs in the currently active editor
  5205. * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
  5206. *
  5207. * // Sets a style value to an element by id in the current document
  5208. * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
  5209. */
  5210. setStyle: function(elm, name, value) {
  5211. return this.run(elm, function(elm) {
  5212. var self = this, style, key;
  5213. if (name) {
  5214. if (typeof(name) === 'string') {
  5215. style = elm.style;
  5216. // Camelcase it, if needed
  5217. name = name.replace(/-(\D)/g, function(a, b) {
  5218. return b.toUpperCase();
  5219. });
  5220. // Default px suffix on these
  5221. if (typeof(value) === 'number' && !numericCssMap[name]) {
  5222. value += 'px';
  5223. }
  5224. // IE specific opacity
  5225. if (name === "opacity" && elm.runtimeStyle && typeof(elm.runtimeStyle.opacity) === "undefined") {
  5226. style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
  5227. }
  5228. if (name == "float") {
  5229. // Old IE vs modern browsers
  5230. name = "cssFloat" in elm.style ? "cssFloat" : "styleFloat";
  5231. }
  5232. try {
  5233. style[name] = value;
  5234. } catch (ex) {
  5235. // Ignore IE errors
  5236. }
  5237. // Force update of the style data
  5238. if (self.settings.update_styles) {
  5239. elm.removeAttribute('data-mce-style');
  5240. }
  5241. } else {
  5242. for (key in name) {
  5243. self.setStyle(elm, key, name[key]);
  5244. }
  5245. }
  5246. }
  5247. });
  5248. },
  5249. /**
  5250. * Returns the current style or runtime/computed value of an element.
  5251. *
  5252. * @method getStyle
  5253. * @param {String/Element} elm HTML element or element id string to get style from.
  5254. * @param {String} name Style name to return.
  5255. * @param {Boolean} computed Computed style.
  5256. * @return {String} Current style or computed style value of an element.
  5257. */
  5258. getStyle: function(elm, name, computed) {
  5259. elm = this.get(elm);
  5260. if (!elm) {
  5261. return;
  5262. }
  5263. // W3C
  5264. if (this.doc.defaultView && computed) {
  5265. // Remove camelcase
  5266. name = name.replace(/[A-Z]/g, function(a){
  5267. return '-' + a;
  5268. });
  5269. try {
  5270. return this.doc.defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  5271. } catch (ex) {
  5272. // Old safari might fail
  5273. return null;
  5274. }
  5275. }
  5276. // Camelcase it, if needed
  5277. name = name.replace(/-(\D)/g, function(a, b) {
  5278. return b.toUpperCase();
  5279. });
  5280. if (name == 'float') {
  5281. name = isIE ? 'styleFloat' : 'cssFloat';
  5282. }
  5283. // IE & Opera
  5284. if (elm.currentStyle && computed) {
  5285. return elm.currentStyle[name];
  5286. }
  5287. return elm.style ? elm.style[name] : undefined;
  5288. },
  5289. /**
  5290. * Sets multiple styles on the specified element(s).
  5291. *
  5292. * @method setStyles
  5293. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
  5294. * @param {Object} o Name/Value collection of style items to add to the element(s).
  5295. * @example
  5296. * // Sets styles on all paragraphs in the currently active editor
  5297. * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
  5298. *
  5299. * // Sets styles to an element by id in the current document
  5300. * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
  5301. */
  5302. setStyles: function(elm, styles) {
  5303. this.setStyle(elm, styles);
  5304. },
  5305. css: function(elm, name, value) {
  5306. this.setStyle(elm, name, value);
  5307. },
  5308. /**
  5309. * Removes all attributes from an element or elements.
  5310. *
  5311. * @method removeAllAttribs
  5312. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
  5313. */
  5314. removeAllAttribs: function(e) {
  5315. return this.run(e, function(e) {
  5316. var i, attrs = e.attributes;
  5317. for (i = attrs.length - 1; i >= 0; i--) {
  5318. e.removeAttributeNode(attrs.item(i));
  5319. }
  5320. });
  5321. },
  5322. /**
  5323. * Sets the specified attribute of an element or elements.
  5324. *
  5325. * @method setAttrib
  5326. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
  5327. * @param {String} n Name of attribute to set.
  5328. * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
  5329. * @example
  5330. * // Sets class attribute on all paragraphs in the active editor
  5331. * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
  5332. *
  5333. * // Sets class attribute on a specific element in the current page
  5334. * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
  5335. */
  5336. setAttrib: function(e, n, v) {
  5337. var self = this;
  5338. // What's the point
  5339. if (!e || !n) {
  5340. return;
  5341. }
  5342. return this.run(e, function(e) {
  5343. var s = self.settings;
  5344. var originalValue = e.getAttribute(n);
  5345. if (v !== null) {
  5346. switch (n) {
  5347. case "style":
  5348. if (!is(v, 'string')) {
  5349. each(v, function(v, n) {
  5350. self.setStyle(e, n, v);
  5351. });
  5352. return;
  5353. }
  5354. // No mce_style for elements with these since they might get resized by the user
  5355. if (s.keep_values) {
  5356. if (v) {
  5357. e.setAttribute('data-mce-style', v, 2);
  5358. } else {
  5359. e.removeAttribute('data-mce-style', 2);
  5360. }
  5361. }
  5362. e.style.cssText = v;
  5363. break;
  5364. case "class":
  5365. e.className = v || ''; // Fix IE null bug
  5366. break;
  5367. case "src":
  5368. case "href":
  5369. if (s.keep_values) {
  5370. if (s.url_converter) {
  5371. v = s.url_converter.call(s.url_converter_scope || self, v, n, e);
  5372. }
  5373. self.setAttrib(e, 'data-mce-' + n, v, 2);
  5374. }
  5375. break;
  5376. case "shape":
  5377. e.setAttribute('data-mce-style', v);
  5378. break;
  5379. }
  5380. }
  5381. if (is(v) && v !== null && v.length !== 0) {
  5382. e.setAttribute(n, '' + v, 2);
  5383. } else {
  5384. e.removeAttribute(n, 2);
  5385. }
  5386. // fire onChangeAttrib event for attributes that have changed
  5387. if (originalValue != v && s.onSetAttrib) {
  5388. s.onSetAttrib({attrElm: e, attrName: n, attrValue: v});
  5389. }
  5390. });
  5391. },
  5392. /**
  5393. * Sets two or more specified attributes of an element or elements.
  5394. *
  5395. * @method setAttribs
  5396. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
  5397. * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
  5398. * @example
  5399. * // Sets class and title attributes on all paragraphs in the active editor
  5400. * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
  5401. *
  5402. * // Sets class and title attributes on a specific element in the current page
  5403. * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
  5404. */
  5405. setAttribs: function(elm, attrs) {
  5406. var self = this;
  5407. return this.run(elm, function(elm) {
  5408. each(attrs, function(value, name) {
  5409. self.setAttrib(elm, name, value);
  5410. });
  5411. });
  5412. },
  5413. /**
  5414. * Returns the specified attribute by name.
  5415. *
  5416. * @method getAttrib
  5417. * @param {String/Element} elm Element string id or DOM element to get attribute from.
  5418. * @param {String} name Name of attribute to get.
  5419. * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
  5420. * @return {String} Attribute value string, default value or null if the attribute wasn't found.
  5421. */
  5422. getAttrib: function(elm, name, defaultVal) {
  5423. var value, self = this, undef;
  5424. elm = self.get(elm);
  5425. if (!elm || elm.nodeType !== 1) {
  5426. return defaultVal === undef ? false : defaultVal;
  5427. }
  5428. if (!is(defaultVal)) {
  5429. defaultVal = '';
  5430. }
  5431. // Try the mce variant for these
  5432. if (/^(src|href|style|coords|shape)$/.test(name)) {
  5433. value = elm.getAttribute("data-mce-" + name);
  5434. if (value) {
  5435. return value;
  5436. }
  5437. }
  5438. if (isIE && self.props[name]) {
  5439. value = elm[self.props[name]];
  5440. value = value && value.nodeValue ? value.nodeValue : value;
  5441. }
  5442. if (!value) {
  5443. value = elm.getAttribute(name, 2);
  5444. }
  5445. // Check boolean attribs
  5446. if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(name)) {
  5447. if (elm[self.props[name]] === true && value === '') {
  5448. return name;
  5449. }
  5450. return value ? name : '';
  5451. }
  5452. // Inner input elements will override attributes on form elements
  5453. if (elm.nodeName === "FORM" && elm.getAttributeNode(name)) {
  5454. return elm.getAttributeNode(name).nodeValue;
  5455. }
  5456. if (name === 'style') {
  5457. value = value || elm.style.cssText;
  5458. if (value) {
  5459. value = self.serializeStyle(self.parseStyle(value), elm.nodeName);
  5460. if (self.settings.keep_values) {
  5461. elm.setAttribute('data-mce-style', value);
  5462. }
  5463. }
  5464. }
  5465. // Remove Apple and WebKit stuff
  5466. if (isWebKit && name === "class" && value) {
  5467. value = value.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
  5468. }
  5469. // Handle IE issues
  5470. if (isIE) {
  5471. switch (name) {
  5472. case 'rowspan':
  5473. case 'colspan':
  5474. // IE returns 1 as default value
  5475. if (value === 1) {
  5476. value = '';
  5477. }
  5478. break;
  5479. case 'size':
  5480. // IE returns +0 as default value for size
  5481. if (value === '+0' || value === 20 || value === 0) {
  5482. value = '';
  5483. }
  5484. break;
  5485. case 'width':
  5486. case 'height':
  5487. case 'vspace':
  5488. case 'checked':
  5489. case 'disabled':
  5490. case 'readonly':
  5491. if (value === 0) {
  5492. value = '';
  5493. }
  5494. break;
  5495. case 'hspace':
  5496. // IE returns -1 as default value
  5497. if (value === -1) {
  5498. value = '';
  5499. }
  5500. break;
  5501. case 'maxlength':
  5502. case 'tabindex':
  5503. // IE returns default value
  5504. if (value === 32768 || value === 2147483647 || value === '32768') {
  5505. value = '';
  5506. }
  5507. break;
  5508. case 'multiple':
  5509. case 'compact':
  5510. case 'noshade':
  5511. case 'nowrap':
  5512. if (value === 65535) {
  5513. return name;
  5514. }
  5515. return defaultVal;
  5516. case 'shape':
  5517. value = value.toLowerCase();
  5518. break;
  5519. default:
  5520. // IE has odd anonymous function for event attributes
  5521. if (name.indexOf('on') === 0 && value) {
  5522. value = ('' + value).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
  5523. }
  5524. }
  5525. }
  5526. return (value !== undef && value !== null && value !== '') ? '' + value : defaultVal;
  5527. },
  5528. /**
  5529. * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
  5530. *
  5531. * @method getPos
  5532. * @param {Element/String} elm HTML element or element id to get x, y position from.
  5533. * @param {Element} rootElm Optional root element to stop calculations at.
  5534. * @return {object} Absolute position of the specified element object with x, y fields.
  5535. */
  5536. getPos: function(elm, rootElm) {
  5537. var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;
  5538. elm = self.get(elm);
  5539. rootElm = rootElm || doc.body;
  5540. if (elm) {
  5541. // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
  5542. if (rootElm === doc.body && elm.getBoundingClientRect) {
  5543. pos = elm.getBoundingClientRect();
  5544. rootElm = self.boxModel ? doc.documentElement : doc.body;
  5545. // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
  5546. // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
  5547. x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft;
  5548. y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop;
  5549. return {x: x, y: y};
  5550. }
  5551. offsetParent = elm;
  5552. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  5553. x += offsetParent.offsetLeft || 0;
  5554. y += offsetParent.offsetTop || 0;
  5555. offsetParent = offsetParent.offsetParent;
  5556. }
  5557. offsetParent = elm.parentNode;
  5558. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  5559. x -= offsetParent.scrollLeft || 0;
  5560. y -= offsetParent.scrollTop || 0;
  5561. offsetParent = offsetParent.parentNode;
  5562. }
  5563. }
  5564. return {x: x, y: y};
  5565. },
  5566. /**
  5567. * Parses the specified style value into an object collection. This parser will also
  5568. * merge and remove any redundant items that browsers might have added. It will also convert non-hex
  5569. * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  5570. *
  5571. * @method parseStyle
  5572. * @param {String} cssText Style value to parse, for example: border:1px solid red;.
  5573. * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
  5574. */
  5575. parseStyle: function(cssText) {
  5576. return this.styles.parse(cssText);
  5577. },
  5578. /**
  5579. * Serializes the specified style object into a string.
  5580. *
  5581. * @method serializeStyle
  5582. * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
  5583. * @param {String} name Optional element name.
  5584. * @return {String} String representation of the style object, for example: border: 1px solid red.
  5585. */
  5586. serializeStyle: function(styles, name) {
  5587. return this.styles.serialize(styles, name);
  5588. },
  5589. /**
  5590. * Adds a style element at the top of the document with the specified cssText content.
  5591. *
  5592. * @method addStyle
  5593. * @param {String} cssText CSS Text style to add to top of head of document.
  5594. */
  5595. addStyle: function(cssText) {
  5596. var self = this, doc = self.doc, head, styleElm;
  5597. // Prevent inline from loading the same styles twice
  5598. if (self !== DOMUtils.DOM && doc === document) {
  5599. var addedStyles = DOMUtils.DOM.addedStyles;
  5600. addedStyles = addedStyles || [];
  5601. if (addedStyles[cssText]) {
  5602. return;
  5603. }
  5604. addedStyles[cssText] = true;
  5605. DOMUtils.DOM.addedStyles = addedStyles;
  5606. }
  5607. // Create style element if needed
  5608. styleElm = doc.getElementById('mceDefaultStyles');
  5609. if (!styleElm) {
  5610. styleElm = doc.createElement('style');
  5611. styleElm.id = 'mceDefaultStyles';
  5612. styleElm.type = 'text/css';
  5613. head = doc.getElementsByTagName('head')[0];
  5614. if (head.firstChild) {
  5615. head.insertBefore(styleElm, head.firstChild);
  5616. } else {
  5617. head.appendChild(styleElm);
  5618. }
  5619. }
  5620. // Append style data to old or new style element
  5621. if (styleElm.styleSheet) {
  5622. styleElm.styleSheet.cssText += cssText;
  5623. } else {
  5624. styleElm.appendChild(doc.createTextNode(cssText));
  5625. }
  5626. },
  5627. /**
  5628. * Imports/loads the specified CSS file into the document bound to the class.
  5629. *
  5630. * @method loadCSS
  5631. * @param {String} u URL to CSS file to load.
  5632. * @example
  5633. * // Loads a CSS file dynamically into the current document
  5634. * tinymce.DOM.loadCSS('somepath/some.css');
  5635. *
  5636. * // Loads a CSS file into the currently active editor instance
  5637. * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
  5638. *
  5639. * // Loads a CSS file into an editor instance by id
  5640. * tinymce.get('someid').dom.loadCSS('somepath/some.css');
  5641. *
  5642. * // Loads multiple CSS files into the current document
  5643. * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
  5644. */
  5645. loadCSS: function(url) {
  5646. var self = this, doc = self.doc, head;
  5647. // Prevent inline from loading the same CSS file twice
  5648. if (self !== DOMUtils.DOM && doc === document) {
  5649. DOMUtils.DOM.loadCSS(url);
  5650. return;
  5651. }
  5652. if (!url) {
  5653. url = '';
  5654. }
  5655. head = doc.getElementsByTagName('head')[0];
  5656. each(url.split(','), function(url) {
  5657. var link;
  5658. if (self.files[url]) {
  5659. return;
  5660. }
  5661. self.files[url] = true;
  5662. link = self.create('link', {rel: 'stylesheet', href: url});
  5663. // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
  5664. // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
  5665. // It's ugly but it seems to work fine.
  5666. if (isIE && doc.documentMode && doc.recalc) {
  5667. link.onload = function() {
  5668. if (doc.recalc) {
  5669. doc.recalc();
  5670. }
  5671. link.onload = null;
  5672. };
  5673. }
  5674. head.appendChild(link);
  5675. });
  5676. },
  5677. /**
  5678. * Adds a class to the specified element or elements.
  5679. *
  5680. * @method addClass
  5681. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  5682. * @param {String} cls Class name to add to each element.
  5683. * @return {String/Array} String with new class value or array with new class values for all elements.
  5684. * @example
  5685. * // Adds a class to all paragraphs in the active editor
  5686. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  5687. *
  5688. * // Adds a class to a specific element in the current page
  5689. * tinymce.DOM.addClass('mydiv', 'myclass');
  5690. */
  5691. addClass: function(elm, cls) {
  5692. return this.run(elm, function(elm) {
  5693. var clsVal;
  5694. if (!cls) {
  5695. return 0;
  5696. }
  5697. if (this.hasClass(elm, cls)) {
  5698. return elm.className;
  5699. }
  5700. clsVal = this.removeClass(elm, cls);
  5701. elm.className = clsVal = (clsVal !== '' ? (clsVal + ' ') : '') + cls;
  5702. return clsVal;
  5703. });
  5704. },
  5705. /**
  5706. * Removes a class from the specified element or elements.
  5707. *
  5708. * @method removeClass
  5709. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  5710. * @param {String} cls Class name to remove from each element.
  5711. * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
  5712. * were passed in.
  5713. * @example
  5714. * // Removes a class from all paragraphs in the active editor
  5715. * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  5716. *
  5717. * // Removes a class from a specific element in the current page
  5718. * tinymce.DOM.removeClass('mydiv', 'myclass');
  5719. */
  5720. removeClass: function(elm, cls) {
  5721. var self = this, re;
  5722. return self.run(elm, function(elm) {
  5723. var val;
  5724. if (self.hasClass(elm, cls)) {
  5725. if (!re) {
  5726. re = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", "g");
  5727. }
  5728. val = elm.className.replace(re, ' ');
  5729. val = trim(val != ' ' ? val : '');
  5730. elm.className = val;
  5731. // Empty class attr
  5732. if (!val) {
  5733. elm.removeAttribute('class');
  5734. elm.removeAttribute('className');
  5735. }
  5736. return val;
  5737. }
  5738. return elm.className;
  5739. });
  5740. },
  5741. /**
  5742. * Returns true if the specified element has the specified class.
  5743. *
  5744. * @method hasClass
  5745. * @param {String/Element} n HTML element or element id string to check CSS class on.
  5746. * @param {String} c CSS class to check for.
  5747. * @return {Boolean} true/false if the specified element has the specified class.
  5748. */
  5749. hasClass: function(elm, cls) {
  5750. elm = this.get(elm);
  5751. if (!elm || !cls) {
  5752. return false;
  5753. }
  5754. return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') !== -1;
  5755. },
  5756. /**
  5757. * Toggles the specified class on/off.
  5758. *
  5759. * @method toggleClass
  5760. * @param {Element} elm Element to toggle class on.
  5761. * @param {[type]} cls Class to toggle on/off.
  5762. * @param {[type]} state Optional state to set.
  5763. */
  5764. toggleClass: function(elm, cls, state) {
  5765. state = state === undefined ? !this.hasClass(elm, cls) : state;
  5766. if (this.hasClass(elm, cls) !== state) {
  5767. if (state) {
  5768. this.addClass(elm, cls);
  5769. } else {
  5770. this.removeClass(elm, cls);
  5771. }
  5772. }
  5773. },
  5774. /**
  5775. * Shows the specified element(s) by ID by setting the "display" style.
  5776. *
  5777. * @method show
  5778. * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
  5779. */
  5780. show: function(elm) {
  5781. return this.setStyle(elm, 'display', 'block');
  5782. },
  5783. /**
  5784. * Hides the specified element(s) by ID by setting the "display" style.
  5785. *
  5786. * @method hide
  5787. * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
  5788. * @example
  5789. * // Hides an element by id in the document
  5790. * tinymce.DOM.hide('myid');
  5791. */
  5792. hide: function(elm) {
  5793. return this.setStyle(elm, 'display', 'none');
  5794. },
  5795. /**
  5796. * Returns true/false if the element is hidden or not by checking the "display" style.
  5797. *
  5798. * @method isHidden
  5799. * @param {String/Element} e Id or element to check display state on.
  5800. * @return {Boolean} true/false if the element is hidden or not.
  5801. */
  5802. isHidden: function(elm) {
  5803. elm = this.get(elm);
  5804. return !elm || elm.style.display == 'none' || this.getStyle(elm, 'display') == 'none';
  5805. },
  5806. /**
  5807. * Returns a unique id. This can be useful when generating elements on the fly.
  5808. * This method will not check if the element already exists.
  5809. *
  5810. * @method uniqueId
  5811. * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
  5812. * @return {String} Unique id.
  5813. */
  5814. uniqueId: function(prefix) {
  5815. return (!prefix ? 'mce_' : prefix) + (this.counter++);
  5816. },
  5817. /**
  5818. * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
  5819. * URLs will get converted, hex color values fixed etc. Check processHTML for details.
  5820. *
  5821. * @method setHTML
  5822. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside of.
  5823. * @param {String} h HTML content to set as inner HTML of the element.
  5824. * @example
  5825. * // Sets the inner HTML of all paragraphs in the active editor
  5826. * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
  5827. *
  5828. * // Sets the inner HTML of an element by id in the document
  5829. * tinymce.DOM.setHTML('mydiv', 'some inner html');
  5830. */
  5831. setHTML: function(element, html) {
  5832. var self = this;
  5833. return self.run(element, function(element) {
  5834. if (isIE) {
  5835. // Remove all child nodes, IE keeps empty text nodes in DOM
  5836. while (element.firstChild) {
  5837. element.removeChild(element.firstChild);
  5838. }
  5839. try {
  5840. // IE will remove comments from the beginning
  5841. // unless you padd the contents with something
  5842. element.innerHTML = '<br />' + html;
  5843. element.removeChild(element.firstChild);
  5844. } catch (ex) {
  5845. // IE sometimes produces an unknown runtime error on innerHTML if it's a block element
  5846. // within a block element for example a div inside a p
  5847. // This seems to fix this problem
  5848. // Create new div with HTML contents and a BR in front to keep comments
  5849. var newElement = self.create('div');
  5850. newElement.innerHTML = '<br />' + html;
  5851. // Add all children from div to target
  5852. each(grep(newElement.childNodes), function(node, i) {
  5853. // Skip br element
  5854. if (i && element.canHaveHTML) {
  5855. element.appendChild(node);
  5856. }
  5857. });
  5858. }
  5859. } else {
  5860. element.innerHTML = html;
  5861. }
  5862. return html;
  5863. });
  5864. },
  5865. /**
  5866. * Returns the outer HTML of an element.
  5867. *
  5868. * @method getOuterHTML
  5869. * @param {String/Element} elm Element ID or element object to get outer HTML from.
  5870. * @return {String} Outer HTML string.
  5871. * @example
  5872. * tinymce.DOM.getOuterHTML(editorElement);
  5873. * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
  5874. */
  5875. getOuterHTML: function(elm) {
  5876. var doc, self = this;
  5877. elm = self.get(elm);
  5878. if (!elm) {
  5879. return null;
  5880. }
  5881. if (elm.nodeType === 1 && self.hasOuterHTML) {
  5882. return elm.outerHTML;
  5883. }
  5884. doc = (elm.ownerDocument || self.doc).createElement("body");
  5885. doc.appendChild(elm.cloneNode(true));
  5886. return doc.innerHTML;
  5887. },
  5888. /**
  5889. * Sets the specified outer HTML on an element or elements.
  5890. *
  5891. * @method setOuterHTML
  5892. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
  5893. * @param {Object} html HTML code to set as outer value for the element.
  5894. * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
  5895. * @example
  5896. * // Sets the outer HTML of all paragraphs in the active editor
  5897. * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
  5898. *
  5899. * // Sets the outer HTML of an element by id in the document
  5900. * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
  5901. */
  5902. setOuterHTML: function(elm, html, doc) {
  5903. var self = this;
  5904. return self.run(elm, function(elm) {
  5905. function set() {
  5906. var node, tempElm;
  5907. tempElm = doc.createElement("body");
  5908. tempElm.innerHTML = html;
  5909. node = tempElm.lastChild;
  5910. while (node) {
  5911. self.insertAfter(node.cloneNode(true), elm);
  5912. node = node.previousSibling;
  5913. }
  5914. self.remove(elm);
  5915. }
  5916. // Only set HTML on elements
  5917. if (elm.nodeType == 1) {
  5918. doc = doc || elm.ownerDocument || self.doc;
  5919. if (isIE) {
  5920. try {
  5921. // Try outerHTML for IE it sometimes produces an unknown runtime error
  5922. if (elm.nodeType == 1 && self.hasOuterHTML) {
  5923. elm.outerHTML = html;
  5924. } else {
  5925. set();
  5926. }
  5927. } catch (ex) {
  5928. // Fix for unknown runtime error
  5929. set();
  5930. }
  5931. } else {
  5932. set();
  5933. }
  5934. }
  5935. });
  5936. },
  5937. /**
  5938. * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
  5939. *
  5940. * @method decode
  5941. * @param {String} s String to decode entities on.
  5942. * @return {String} Entity decoded string.
  5943. */
  5944. decode: Entities.decode,
  5945. /**
  5946. * Entity encodes a string. This method encodes the most common entities, such as <>"&.
  5947. *
  5948. * @method encode
  5949. * @param {String} text String to encode with entities.
  5950. * @return {String} Entity encoded string.
  5951. */
  5952. encode: Entities.encodeAllRaw,
  5953. /**
  5954. * Inserts an element after the reference element.
  5955. *
  5956. * @method insertAfter
  5957. * @param {Element} node Element to insert after the reference.
  5958. * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
  5959. * @return {Element/Array} Element that got added or an array with elements.
  5960. */
  5961. insertAfter: function(node, reference_node) {
  5962. reference_node = this.get(reference_node);
  5963. return this.run(node, function(node) {
  5964. var parent, nextSibling;
  5965. parent = reference_node.parentNode;
  5966. nextSibling = reference_node.nextSibling;
  5967. if (nextSibling) {
  5968. parent.insertBefore(node, nextSibling);
  5969. } else {
  5970. parent.appendChild(node);
  5971. }
  5972. return node;
  5973. });
  5974. },
  5975. /**
  5976. * Replaces the specified element or elements with the new element specified. The new element will
  5977. * be cloned if multiple input elements are passed in.
  5978. *
  5979. * @method replace
  5980. * @param {Element} newElm New element to replace old ones with.
  5981. * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
  5982. * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
  5983. */
  5984. replace: function(newElm, oldElm, keepChildren) {
  5985. var self = this;
  5986. return self.run(oldElm, function(oldElm) {
  5987. if (is(oldElm, 'array')) {
  5988. newElm = newElm.cloneNode(true);
  5989. }
  5990. if (keepChildren) {
  5991. each(grep(oldElm.childNodes), function(node) {
  5992. newElm.appendChild(node);
  5993. });
  5994. }
  5995. return oldElm.parentNode.replaceChild(newElm, oldElm);
  5996. });
  5997. },
  5998. /**
  5999. * Renames the specified element and keeps its attributes and children.
  6000. *
  6001. * @method rename
  6002. * @param {Element} elm Element to rename.
  6003. * @param {String} name Name of the new element.
  6004. * @return {Element} New element or the old element if it needed renaming.
  6005. */
  6006. rename: function(elm, name) {
  6007. var self = this, newElm;
  6008. if (elm.nodeName != name.toUpperCase()) {
  6009. // Rename block element
  6010. newElm = self.create(name);
  6011. // Copy attribs to new block
  6012. each(self.getAttribs(elm), function(attr_node) {
  6013. self.setAttrib(newElm, attr_node.nodeName, self.getAttrib(elm, attr_node.nodeName));
  6014. });
  6015. // Replace block
  6016. self.replace(newElm, elm, 1);
  6017. }
  6018. return newElm || elm;
  6019. },
  6020. /**
  6021. * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
  6022. *
  6023. * @method findCommonAncestor
  6024. * @param {Element} a Element to find common ancestor of.
  6025. * @param {Element} b Element to find common ancestor of.
  6026. * @return {Element} Common ancestor element of the two input elements.
  6027. */
  6028. findCommonAncestor: function(a, b) {
  6029. var ps = a, pe;
  6030. while (ps) {
  6031. pe = b;
  6032. while (pe && ps != pe) {
  6033. pe = pe.parentNode;
  6034. }
  6035. if (ps == pe) {
  6036. break;
  6037. }
  6038. ps = ps.parentNode;
  6039. }
  6040. if (!ps && a.ownerDocument) {
  6041. return a.ownerDocument.documentElement;
  6042. }
  6043. return ps;
  6044. },
  6045. /**
  6046. * Parses the specified RGB color value and returns a hex version of that color.
  6047. *
  6048. * @method toHex
  6049. * @param {String} rgbVal RGB string value like rgb(1,2,3)
  6050. * @return {String} Hex version of that RGB value like #FF00FF.
  6051. */
  6052. toHex: function(rgbVal) {
  6053. return this.styles.toHex(Tools.trim(rgbVal));
  6054. },
  6055. /**
  6056. * Executes the specified function on the element by id or dom element node or array of elements/id.
  6057. *
  6058. * @method run
  6059. * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
  6060. * @param {function} f Function to execute for each item.
  6061. * @param {Object} s Optional scope to execute the function in.
  6062. * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
  6063. */
  6064. run: function(elm, func, scope) {
  6065. var self = this, result;
  6066. if (typeof(elm) === 'string') {
  6067. elm = self.get(elm);
  6068. }
  6069. if (!elm) {
  6070. return false;
  6071. }
  6072. scope = scope || this;
  6073. if (!elm.nodeType && (elm.length || elm.length === 0)) {
  6074. result = [];
  6075. each(elm, function(elm, i) {
  6076. if (elm) {
  6077. if (typeof(elm) == 'string') {
  6078. elm = self.get(elm);
  6079. }
  6080. result.push(func.call(scope, elm, i));
  6081. }
  6082. });
  6083. return result;
  6084. }
  6085. return func.call(scope, elm);
  6086. },
  6087. /**
  6088. * Returns a NodeList with attributes for the element.
  6089. *
  6090. * @method getAttribs
  6091. * @param {HTMLElement/string} elm Element node or string id to get attributes from.
  6092. * @return {NodeList} NodeList with attributes.
  6093. */
  6094. getAttribs: function(elm) {
  6095. var attrs;
  6096. elm = this.get(elm);
  6097. if (!elm) {
  6098. return [];
  6099. }
  6100. if (isIE) {
  6101. attrs = [];
  6102. // Object will throw exception in IE
  6103. if (elm.nodeName == 'OBJECT') {
  6104. return elm.attributes;
  6105. }
  6106. // IE doesn't keep the selected attribute if you clone option elements
  6107. if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
  6108. attrs.push({specified: 1, nodeName: 'selected'});
  6109. }
  6110. // It's crazy that this is faster in IE but it's because it returns all attributes all the time
  6111. var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
  6112. elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
  6113. attrs.push({specified: 1, nodeName: a});
  6114. });
  6115. return attrs;
  6116. }
  6117. return elm.attributes;
  6118. },
  6119. /**
  6120. * Returns true/false if the specified node is to be considered empty or not.
  6121. *
  6122. * @example
  6123. * tinymce.DOM.isEmpty(node, {img: true});
  6124. * @method isEmpty
  6125. * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
  6126. * @return {Boolean} true/false if the node is empty or not.
  6127. */
  6128. isEmpty: function(node, elements) {
  6129. var self = this, i, attributes, type, walker, name, brCount = 0;
  6130. node = node.firstChild;
  6131. if (node) {
  6132. walker = new TreeWalker(node, node.parentNode);
  6133. elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
  6134. do {
  6135. type = node.nodeType;
  6136. if (type === 1) {
  6137. // Ignore bogus elements
  6138. if (node.getAttribute('data-mce-bogus')) {
  6139. continue;
  6140. }
  6141. // Keep empty elements like <img />
  6142. name = node.nodeName.toLowerCase();
  6143. if (elements && elements[name]) {
  6144. // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
  6145. if (name === 'br') {
  6146. brCount++;
  6147. continue;
  6148. }
  6149. return false;
  6150. }
  6151. // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
  6152. attributes = self.getAttribs(node);
  6153. i = attributes.length;
  6154. while (i--) {
  6155. name = attributes[i].nodeName;
  6156. if (name === "name" || name === 'data-mce-bookmark') {
  6157. return false;
  6158. }
  6159. }
  6160. }
  6161. // Keep comment nodes
  6162. if (type == 8) {
  6163. return false;
  6164. }
  6165. // Keep non whitespace text nodes
  6166. if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
  6167. return false;
  6168. }
  6169. } while ((node = walker.next()));
  6170. }
  6171. return brCount <= 1;
  6172. },
  6173. /**
  6174. * Creates a new DOM Range object. This will use the native DOM Range API if it's
  6175. * available. If it's not, it will fall back to the custom TinyMCE implementation.
  6176. *
  6177. * @method createRng
  6178. * @return {DOMRange} DOM Range object.
  6179. * @example
  6180. * var rng = tinymce.DOM.createRng();
  6181. * alert(rng.startContainer + "," + rng.startOffset);
  6182. */
  6183. createRng: function() {
  6184. var doc = this.doc;
  6185. return doc.createRange ? doc.createRange() : new Range(this);
  6186. },
  6187. /**
  6188. * Returns the index of the specified node within its parent.
  6189. *
  6190. * @method nodeIndex
  6191. * @param {Node} node Node to look for.
  6192. * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
  6193. * @return {Number} Index of the specified node.
  6194. */
  6195. nodeIndex: function(node, normalized) {
  6196. var idx = 0, lastNodeType, nodeType;
  6197. if (node) {
  6198. for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
  6199. nodeType = node.nodeType;
  6200. // Normalize text nodes
  6201. if (normalized && nodeType == 3) {
  6202. if (nodeType == lastNodeType || !node.nodeValue.length) {
  6203. continue;
  6204. }
  6205. }
  6206. idx++;
  6207. lastNodeType = nodeType;
  6208. }
  6209. }
  6210. return idx;
  6211. },
  6212. /**
  6213. * Splits an element into two new elements and places the specified split
  6214. * element or elements between the new ones. For example splitting the paragraph at the bold element in
  6215. * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
  6216. *
  6217. * @method split
  6218. * @param {Element} parentElm Parent element to split.
  6219. * @param {Element} splitElm Element to split at.
  6220. * @param {Element} replacementElm Optional replacement element to replace the split element with.
  6221. * @return {Element} Returns the split element or the replacement element if that is specified.
  6222. */
  6223. split: function(parentElm, splitElm, replacementElm) {
  6224. var self = this, r = self.createRng(), bef, aft, pa;
  6225. // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
  6226. // but we don't want that in our code since it serves no purpose for the end user
  6227. // For example splitting this html at the bold element:
  6228. // <p>text 1<span><b>CHOP</b></span>text 2</p>
  6229. // would produce:
  6230. // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
  6231. // this function will then trim off empty edges and produce:
  6232. // <p>text 1</p><b>CHOP</b><p>text 2</p>
  6233. function trimNode(node) {
  6234. var i, children = node.childNodes, type = node.nodeType;
  6235. function surroundedBySpans(node) {
  6236. var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
  6237. var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
  6238. return previousIsSpan && nextIsSpan;
  6239. }
  6240. if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
  6241. return;
  6242. }
  6243. for (i = children.length - 1; i >= 0; i--) {
  6244. trimNode(children[i]);
  6245. }
  6246. if (type != 9) {
  6247. // Keep non whitespace text nodes
  6248. if (type == 3 && node.nodeValue.length > 0) {
  6249. // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
  6250. // Also keep text nodes with only spaces if surrounded by spans.
  6251. // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
  6252. var trimmedLength = trim(node.nodeValue).length;
  6253. if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
  6254. return;
  6255. }
  6256. } else if (type == 1) {
  6257. // If the only child is a bookmark then move it up
  6258. children = node.childNodes;
  6259. // TODO fix this complex if
  6260. if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
  6261. children[0].getAttribute('data-mce-type') == 'bookmark') {
  6262. node.parentNode.insertBefore(children[0], node);
  6263. }
  6264. // Keep non empty elements or img, hr etc
  6265. if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
  6266. return;
  6267. }
  6268. }
  6269. self.remove(node);
  6270. }
  6271. return node;
  6272. }
  6273. if (parentElm && splitElm) {
  6274. // Get before chunk
  6275. r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
  6276. r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
  6277. bef = r.extractContents();
  6278. // Get after chunk
  6279. r = self.createRng();
  6280. r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
  6281. r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
  6282. aft = r.extractContents();
  6283. // Insert before chunk
  6284. pa = parentElm.parentNode;
  6285. pa.insertBefore(trimNode(bef), parentElm);
  6286. // Insert middle chunk
  6287. if (replacementElm) {
  6288. pa.replaceChild(replacementElm, splitElm);
  6289. } else {
  6290. pa.insertBefore(splitElm, parentElm);
  6291. }
  6292. // Insert after chunk
  6293. pa.insertBefore(trimNode(aft), parentElm);
  6294. self.remove(parentElm);
  6295. return replacementElm || splitElm;
  6296. }
  6297. },
  6298. /**
  6299. * Adds an event handler to the specified object.
  6300. *
  6301. * @method bind
  6302. * @param {Element/Document/Window/Array} target Target element to bind events to.
  6303. * handler to or an array of elements/ids/documents.
  6304. * @param {String} name Name of event handler to add, for example: click.
  6305. * @param {function} func Function to execute when the event occurs.
  6306. * @param {Object} scope Optional scope to execute the function in.
  6307. * @return {function} Function callback handler the same as the one passed in.
  6308. */
  6309. bind: function(target, name, func, scope) {
  6310. var self = this;
  6311. if (Tools.isArray(target)) {
  6312. var i = target.length;
  6313. while (i--) {
  6314. target[i] = self.bind(target[i], name, func, scope);
  6315. }
  6316. return target;
  6317. }
  6318. // Collect all window/document events bound by editor instance
  6319. if (self.settings.collect && (target === self.doc || target === self.win)) {
  6320. self.boundEvents.push([target, name, func, scope]);
  6321. }
  6322. return self.events.bind(target, name, func, scope || self);
  6323. },
  6324. /**
  6325. * Removes the specified event handler by name and function from an element or collection of elements.
  6326. *
  6327. * @method unbind
  6328. * @param {Element/Document/Window/Array} target Target element to unbind events on.
  6329. * @param {String} name Event handler name, for example: "click"
  6330. * @param {function} func Function to remove.
  6331. * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
  6332. * were passed in.
  6333. */
  6334. unbind: function(target, name, func) {
  6335. var self = this, i;
  6336. if (Tools.isArray(target)) {
  6337. i = target.length;
  6338. while (i--) {
  6339. target[i] = self.unbind(target[i], name, func);
  6340. }
  6341. return target;
  6342. }
  6343. // Remove any bound events matching the input
  6344. if (self.boundEvents && (target === self.doc || target === self.win)) {
  6345. i = self.boundEvents.length;
  6346. while (i--) {
  6347. var item = self.boundEvents[i];
  6348. if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
  6349. this.events.unbind(item[0], item[1], item[2]);
  6350. }
  6351. }
  6352. }
  6353. return this.events.unbind(target, name, func);
  6354. },
  6355. /**
  6356. * Fires the specified event name with object on target.
  6357. *
  6358. * @method fire
  6359. * @param {Node/Document/Window} target Target element or object to fire event on.
  6360. * @param {String} name Name of the event to fire.
  6361. * @param {Object} evt Event object to send.
  6362. * @return {Event} Event object.
  6363. */
  6364. fire: function(target, name, evt) {
  6365. return this.events.fire(target, name, evt);
  6366. },
  6367. // Returns the content editable state of a node
  6368. getContentEditable: function(node) {
  6369. var contentEditable;
  6370. // Check type
  6371. if (!node || node.nodeType != 1) {
  6372. return null;
  6373. }
  6374. // Check for fake content editable
  6375. contentEditable = node.getAttribute("data-mce-contenteditable");
  6376. if (contentEditable && contentEditable !== "inherit") {
  6377. return contentEditable;
  6378. }
  6379. // Check for real content editable
  6380. return node.contentEditable !== "inherit" ? node.contentEditable : null;
  6381. },
  6382. getContentEditableParent: function(node) {
  6383. var root = this.getRoot(), state = null;
  6384. for (; node && node !== root; node = node.parentNode) {
  6385. state = this.getContentEditable(node);
  6386. if (state !== null) {
  6387. break;
  6388. }
  6389. }
  6390. return state;
  6391. },
  6392. /**
  6393. * Destroys all internal references to the DOM to solve IE leak issues.
  6394. *
  6395. * @method destroy
  6396. */
  6397. destroy: function() {
  6398. var self = this;
  6399. // Unbind all events bound to window/document by editor instance
  6400. if (self.boundEvents) {
  6401. var i = self.boundEvents.length;
  6402. while (i--) {
  6403. var item = self.boundEvents[i];
  6404. this.events.unbind(item[0], item[1], item[2]);
  6405. }
  6406. self.boundEvents = null;
  6407. }
  6408. // Restore sizzle document to window.document
  6409. // Since the current document might be removed producing "Permission denied" on IE see #6325
  6410. if (Sizzle.setDocument) {
  6411. Sizzle.setDocument();
  6412. }
  6413. self.win = self.doc = self.root = self.events = self.frag = null;
  6414. },
  6415. isChildOf: function(node, parent) {
  6416. while (node) {
  6417. if (parent === node) {
  6418. return true;
  6419. }
  6420. node = node.parentNode;
  6421. }
  6422. return false;
  6423. },
  6424. // #ifdef debug
  6425. dumpRng: function(r) {
  6426. return (
  6427. 'startContainer: ' + r.startContainer.nodeName +
  6428. ', startOffset: ' + r.startOffset +
  6429. ', endContainer: ' + r.endContainer.nodeName +
  6430. ', endOffset: ' + r.endOffset
  6431. );
  6432. },
  6433. // #endif
  6434. _findSib: function(node, selector, name) {
  6435. var self = this, func = selector;
  6436. if (node) {
  6437. // If expression make a function of it using is
  6438. if (typeof(func) == 'string') {
  6439. func = function(node) {
  6440. return self.is(node, selector);
  6441. };
  6442. }
  6443. // Loop all siblings
  6444. for (node = node[name]; node; node = node[name]) {
  6445. if (func(node)) {
  6446. return node;
  6447. }
  6448. }
  6449. }
  6450. return null;
  6451. }
  6452. };
  6453. /**
  6454. * Instance of DOMUtils for the current document.
  6455. *
  6456. * @static
  6457. * @property DOM
  6458. * @type tinymce.dom.DOMUtils
  6459. * @example
  6460. * // Example of how to add a class to some element by id
  6461. * tinymce.DOM.addClass('someid', 'someclass');
  6462. */
  6463. DOMUtils.DOM = new DOMUtils(document);
  6464. return DOMUtils;
  6465. });
  6466. // Included from: js/tinymce/classes/dom/ScriptLoader.js
  6467. /**
  6468. * ScriptLoader.js
  6469. *
  6470. * Copyright, Moxiecode Systems AB
  6471. * Released under LGPL License.
  6472. *
  6473. * License: http://www.tinymce.com/license
  6474. * Contributing: http://www.tinymce.com/contributing
  6475. */
  6476. /*globals console*/
  6477. /**
  6478. * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
  6479. * when various items gets loaded. This class is useful to load external JavaScript files.
  6480. *
  6481. * @class tinymce.dom.ScriptLoader
  6482. * @example
  6483. * // Load a script from a specific URL using the global script loader
  6484. * tinymce.ScriptLoader.load('somescript.js');
  6485. *
  6486. * // Load a script using a unique instance of the script loader
  6487. * var scriptLoader = new tinymce.dom.ScriptLoader();
  6488. *
  6489. * scriptLoader.load('somescript.js');
  6490. *
  6491. * // Load multiple scripts
  6492. * var scriptLoader = new tinymce.dom.ScriptLoader();
  6493. *
  6494. * scriptLoader.add('somescript1.js');
  6495. * scriptLoader.add('somescript2.js');
  6496. * scriptLoader.add('somescript3.js');
  6497. *
  6498. * scriptLoader.loadQueue(function() {
  6499. * alert('All scripts are now loaded.');
  6500. * });
  6501. */
  6502. define("tinymce/dom/ScriptLoader", [
  6503. "tinymce/dom/DOMUtils",
  6504. "tinymce/util/Tools"
  6505. ], function(DOMUtils, Tools) {
  6506. var DOM = DOMUtils.DOM;
  6507. var each = Tools.each, grep = Tools.grep;
  6508. function ScriptLoader() {
  6509. var QUEUED = 0,
  6510. LOADING = 1,
  6511. LOADED = 2,
  6512. states = {},
  6513. queue = [],
  6514. scriptLoadedCallbacks = {},
  6515. queueLoadedCallbacks = [],
  6516. loading = 0,
  6517. undef;
  6518. /**
  6519. * Loads a specific script directly without adding it to the load queue.
  6520. *
  6521. * @method load
  6522. * @param {String} url Absolute URL to script to add.
  6523. * @param {function} callback Optional callback function to execute ones this script gets loaded.
  6524. * @param {Object} scope Optional scope to execute callback in.
  6525. */
  6526. function loadScript(url, callback) {
  6527. var dom = DOM, elm, id;
  6528. // Execute callback when script is loaded
  6529. function done() {
  6530. dom.remove(id);
  6531. if (elm) {
  6532. elm.onreadystatechange = elm.onload = elm = null;
  6533. }
  6534. callback();
  6535. }
  6536. function error() {
  6537. /*eslint no-console:0 */
  6538. // Report the error so it's easier for people to spot loading errors
  6539. if (typeof(console) !== "undefined" && console.log) {
  6540. console.log("Failed to load: " + url);
  6541. }
  6542. // We can't mark it as done if there is a load error since
  6543. // A) We don't want to produce 404 errors on the server and
  6544. // B) the onerror event won't fire on all browsers.
  6545. // done();
  6546. }
  6547. id = dom.uniqueId();
  6548. // Create new script element
  6549. elm = document.createElement('script');
  6550. elm.id = id;
  6551. elm.type = 'text/javascript';
  6552. elm.src = url;
  6553. // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
  6554. if ("onreadystatechange" in elm) {
  6555. elm.onreadystatechange = function() {
  6556. if (/loaded|complete/.test(elm.readyState)) {
  6557. done();
  6558. }
  6559. };
  6560. } else {
  6561. elm.onload = done;
  6562. }
  6563. // Add onerror event will get fired on some browsers but not all of them
  6564. elm.onerror = error;
  6565. // Add script to document
  6566. (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
  6567. }
  6568. /**
  6569. * Returns true/false if a script has been loaded or not.
  6570. *
  6571. * @method isDone
  6572. * @param {String} url URL to check for.
  6573. * @return {Boolean} true/false if the URL is loaded.
  6574. */
  6575. this.isDone = function(url) {
  6576. return states[url] == LOADED;
  6577. };
  6578. /**
  6579. * Marks a specific script to be loaded. This can be useful if a script got loaded outside
  6580. * the script loader or to skip it from loading some script.
  6581. *
  6582. * @method markDone
  6583. * @param {string} u Absolute URL to the script to mark as loaded.
  6584. */
  6585. this.markDone = function(url) {
  6586. states[url] = LOADED;
  6587. };
  6588. /**
  6589. * Adds a specific script to the load queue of the script loader.
  6590. *
  6591. * @method add
  6592. * @param {String} url Absolute URL to script to add.
  6593. * @param {function} callback Optional callback function to execute ones this script gets loaded.
  6594. * @param {Object} scope Optional scope to execute callback in.
  6595. */
  6596. this.add = this.load = function(url, callback, scope) {
  6597. var state = states[url];
  6598. // Add url to load queue
  6599. if (state == undef) {
  6600. queue.push(url);
  6601. states[url] = QUEUED;
  6602. }
  6603. if (callback) {
  6604. // Store away callback for later execution
  6605. if (!scriptLoadedCallbacks[url]) {
  6606. scriptLoadedCallbacks[url] = [];
  6607. }
  6608. scriptLoadedCallbacks[url].push({
  6609. func: callback,
  6610. scope: scope || this
  6611. });
  6612. }
  6613. };
  6614. /**
  6615. * Starts the loading of the queue.
  6616. *
  6617. * @method loadQueue
  6618. * @param {function} callback Optional callback to execute when all queued items are loaded.
  6619. * @param {Object} scope Optional scope to execute the callback in.
  6620. */
  6621. this.loadQueue = function(callback, scope) {
  6622. this.loadScripts(queue, callback, scope);
  6623. };
  6624. /**
  6625. * Loads the specified queue of files and executes the callback ones they are loaded.
  6626. * This method is generally not used outside this class but it might be useful in some scenarios.
  6627. *
  6628. * @method loadScripts
  6629. * @param {Array} scripts Array of queue items to load.
  6630. * @param {function} callback Optional callback to execute ones all items are loaded.
  6631. * @param {Object} scope Optional scope to execute callback in.
  6632. */
  6633. this.loadScripts = function(scripts, callback, scope) {
  6634. var loadScripts;
  6635. function execScriptLoadedCallbacks(url) {
  6636. // Execute URL callback functions
  6637. each(scriptLoadedCallbacks[url], function(callback) {
  6638. callback.func.call(callback.scope);
  6639. });
  6640. scriptLoadedCallbacks[url] = undef;
  6641. }
  6642. queueLoadedCallbacks.push({
  6643. func: callback,
  6644. scope: scope || this
  6645. });
  6646. loadScripts = function() {
  6647. var loadingScripts = grep(scripts);
  6648. // Current scripts has been handled
  6649. scripts.length = 0;
  6650. // Load scripts that needs to be loaded
  6651. each(loadingScripts, function(url) {
  6652. // Script is already loaded then execute script callbacks directly
  6653. if (states[url] == LOADED) {
  6654. execScriptLoadedCallbacks(url);
  6655. return;
  6656. }
  6657. // Is script not loading then start loading it
  6658. if (states[url] != LOADING) {
  6659. states[url] = LOADING;
  6660. loading++;
  6661. loadScript(url, function() {
  6662. states[url] = LOADED;
  6663. loading--;
  6664. execScriptLoadedCallbacks(url);
  6665. // Load more scripts if they where added by the recently loaded script
  6666. loadScripts();
  6667. });
  6668. }
  6669. });
  6670. // No scripts are currently loading then execute all pending queue loaded callbacks
  6671. if (!loading) {
  6672. each(queueLoadedCallbacks, function(callback) {
  6673. callback.func.call(callback.scope);
  6674. });
  6675. queueLoadedCallbacks.length = 0;
  6676. }
  6677. };
  6678. loadScripts();
  6679. };
  6680. }
  6681. ScriptLoader.ScriptLoader = new ScriptLoader();
  6682. return ScriptLoader;
  6683. });
  6684. // Included from: js/tinymce/classes/AddOnManager.js
  6685. /**
  6686. * AddOnManager.js
  6687. *
  6688. * Copyright, Moxiecode Systems AB
  6689. * Released under LGPL License.
  6690. *
  6691. * License: http://www.tinymce.com/license
  6692. * Contributing: http://www.tinymce.com/contributing
  6693. */
  6694. /**
  6695. * This class handles the loading of themes/plugins or other add-ons and their language packs.
  6696. *
  6697. * @class tinymce.AddOnManager
  6698. */
  6699. define("tinymce/AddOnManager", [
  6700. "tinymce/dom/ScriptLoader",
  6701. "tinymce/util/Tools"
  6702. ], function(ScriptLoader, Tools) {
  6703. var each = Tools.each;
  6704. function AddOnManager() {
  6705. var self = this;
  6706. self.items = [];
  6707. self.urls = {};
  6708. self.lookup = {};
  6709. }
  6710. AddOnManager.prototype = {
  6711. /**
  6712. * Returns the specified add on by the short name.
  6713. *
  6714. * @method get
  6715. * @param {String} name Add-on to look for.
  6716. * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
  6717. */
  6718. get: function(name) {
  6719. if (this.lookup[name]) {
  6720. return this.lookup[name].instance;
  6721. } else {
  6722. return undefined;
  6723. }
  6724. },
  6725. dependencies: function(name) {
  6726. var result;
  6727. if (this.lookup[name]) {
  6728. result = this.lookup[name].dependencies;
  6729. }
  6730. return result || [];
  6731. },
  6732. /**
  6733. * Loads a language pack for the specified add-on.
  6734. *
  6735. * @method requireLangPack
  6736. * @param {String} name Short name of the add-on.
  6737. * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
  6738. */
  6739. requireLangPack: function(name, languages) {
  6740. var language = AddOnManager.language;
  6741. if (language && AddOnManager.languageLoad !== false) {
  6742. if (languages) {
  6743. languages = ',' + languages + ',';
  6744. // Load short form sv.js or long form sv_SE.js
  6745. if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
  6746. language = language.substr(0, 2);
  6747. } else if (languages.indexOf(',' + language + ',') == -1) {
  6748. return;
  6749. }
  6750. }
  6751. ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
  6752. }
  6753. },
  6754. /**
  6755. * Adds a instance of the add-on by it's short name.
  6756. *
  6757. * @method add
  6758. * @param {String} id Short name/id for the add-on.
  6759. * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
  6760. * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
  6761. * @example
  6762. * // Create a simple plugin
  6763. * tinymce.create('tinymce.plugins.TestPlugin', {
  6764. * TestPlugin: function(ed, url) {
  6765. * ed.on('click', function(e) {
  6766. * ed.windowManager.alert('Hello World!');
  6767. * });
  6768. * }
  6769. * });
  6770. *
  6771. * // Register plugin using the add method
  6772. * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
  6773. *
  6774. * // Initialize TinyMCE
  6775. * tinymce.init({
  6776. * ...
  6777. * plugins: '-test' // Init the plugin but don't try to load it
  6778. * });
  6779. */
  6780. add: function(id, addOn, dependencies) {
  6781. this.items.push(addOn);
  6782. this.lookup[id] = {instance: addOn, dependencies: dependencies};
  6783. return addOn;
  6784. },
  6785. createUrl: function(baseUrl, dep) {
  6786. if (typeof dep === "object") {
  6787. return dep;
  6788. } else {
  6789. return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
  6790. }
  6791. },
  6792. /**
  6793. * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
  6794. * This should be used in development mode. A new compressor/javascript munger process will ensure that the
  6795. * components are put together into the plugin.js file and compressed correctly.
  6796. *
  6797. * @method addComponents
  6798. * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
  6799. * @param {Array} scripts Array containing the names of the scripts to load.
  6800. */
  6801. addComponents: function(pluginName, scripts) {
  6802. var pluginUrl = this.urls[pluginName];
  6803. each(scripts, function(script) {
  6804. ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
  6805. });
  6806. },
  6807. /**
  6808. * Loads an add-on from a specific url.
  6809. *
  6810. * @method load
  6811. * @param {String} name Short name of the add-on that gets loaded.
  6812. * @param {String} addOnUrl URL to the add-on that will get loaded.
  6813. * @param {function} callback Optional callback to execute ones the add-on is loaded.
  6814. * @param {Object} scope Optional scope to execute the callback in.
  6815. * @example
  6816. * // Loads a plugin from an external URL
  6817. * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
  6818. *
  6819. * // Initialize TinyMCE
  6820. * tinymce.init({
  6821. * ...
  6822. * plugins: '-myplugin' // Don't try to load it again
  6823. * });
  6824. */
  6825. load: function(name, addOnUrl, callback, scope) {
  6826. var self = this, url = addOnUrl;
  6827. function loadDependencies() {
  6828. var dependencies = self.dependencies(name);
  6829. each(dependencies, function(dep) {
  6830. var newUrl = self.createUrl(addOnUrl, dep);
  6831. self.load(newUrl.resource, newUrl, undefined, undefined);
  6832. });
  6833. if (callback) {
  6834. if (scope) {
  6835. callback.call(scope);
  6836. } else {
  6837. callback.call(ScriptLoader);
  6838. }
  6839. }
  6840. }
  6841. if (self.urls[name]) {
  6842. return;
  6843. }
  6844. if (typeof addOnUrl === "object") {
  6845. url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
  6846. }
  6847. if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
  6848. url = AddOnManager.baseURL + '/' + url;
  6849. }
  6850. self.urls[name] = url.substring(0, url.lastIndexOf('/'));
  6851. if (self.lookup[name]) {
  6852. loadDependencies();
  6853. } else {
  6854. ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
  6855. }
  6856. }
  6857. };
  6858. AddOnManager.PluginManager = new AddOnManager();
  6859. AddOnManager.ThemeManager = new AddOnManager();
  6860. return AddOnManager;
  6861. });
  6862. /**
  6863. * TinyMCE theme class.
  6864. *
  6865. * @class tinymce.Theme
  6866. */
  6867. /**
  6868. * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
  6869. *
  6870. * @method renderUI
  6871. * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
  6872. * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
  6873. */
  6874. /**
  6875. * 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.
  6876. *
  6877. * @class tinymce.Plugin
  6878. * @example
  6879. * tinymce.PluginManager.add('example', function(editor, url) {
  6880. * // Add a button that opens a window
  6881. * editor.addButton('example', {
  6882. * text: 'My button',
  6883. * icon: false,
  6884. * onclick: function() {
  6885. * // Open window
  6886. * editor.windowManager.open({
  6887. * title: 'Example plugin',
  6888. * body: [
  6889. * {type: 'textbox', name: 'title', label: 'Title'}
  6890. * ],
  6891. * onsubmit: function(e) {
  6892. * // Insert content when the window form is submitted
  6893. * editor.insertContent('Title: ' + e.data.title);
  6894. * }
  6895. * });
  6896. * }
  6897. * });
  6898. *
  6899. * // Adds a menu item to the tools menu
  6900. * editor.addMenuItem('example', {
  6901. * text: 'Example plugin',
  6902. * context: 'tools',
  6903. * onclick: function() {
  6904. * // Open window with a specific url
  6905. * editor.windowManager.open({
  6906. * title: 'TinyMCE site',
  6907. * url: 'http://www.tinymce.com',
  6908. * width: 800,
  6909. * height: 600,
  6910. * buttons: [{
  6911. * text: 'Close',
  6912. * onclick: 'close'
  6913. * }]
  6914. * });
  6915. * }
  6916. * });
  6917. * });
  6918. */
  6919. // Included from: js/tinymce/classes/html/Node.js
  6920. /**
  6921. * Node.js
  6922. *
  6923. * Copyright, Moxiecode Systems AB
  6924. * Released under LGPL License.
  6925. *
  6926. * License: http://www.tinymce.com/license
  6927. * Contributing: http://www.tinymce.com/contributing
  6928. */
  6929. /**
  6930. * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
  6931. *
  6932. * @example
  6933. * var node = new tinymce.html.Node('strong', 1);
  6934. * someRoot.append(node);
  6935. *
  6936. * @class tinymce.html.Node
  6937. * @version 3.4
  6938. */
  6939. define("tinymce/html/Node", [], function() {
  6940. var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
  6941. '#text': 3,
  6942. '#comment': 8,
  6943. '#cdata': 4,
  6944. '#pi': 7,
  6945. '#doctype': 10,
  6946. '#document-fragment': 11
  6947. };
  6948. // Walks the tree left/right
  6949. function walk(node, root_node, prev) {
  6950. var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
  6951. // Walk into nodes if it has a start
  6952. if (node[startName]) {
  6953. return node[startName];
  6954. }
  6955. // Return the sibling if it has one
  6956. if (node !== root_node) {
  6957. sibling = node[siblingName];
  6958. if (sibling) {
  6959. return sibling;
  6960. }
  6961. // Walk up the parents to look for siblings
  6962. for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
  6963. sibling = parent[siblingName];
  6964. if (sibling) {
  6965. return sibling;
  6966. }
  6967. }
  6968. }
  6969. }
  6970. /**
  6971. * Constructs a new Node instance.
  6972. *
  6973. * @constructor
  6974. * @method Node
  6975. * @param {String} name Name of the node type.
  6976. * @param {Number} type Numeric type representing the node.
  6977. */
  6978. function Node(name, type) {
  6979. this.name = name;
  6980. this.type = type;
  6981. if (type === 1) {
  6982. this.attributes = [];
  6983. this.attributes.map = {};
  6984. }
  6985. }
  6986. Node.prototype = {
  6987. /**
  6988. * Replaces the current node with the specified one.
  6989. *
  6990. * @example
  6991. * someNode.replace(someNewNode);
  6992. *
  6993. * @method replace
  6994. * @param {tinymce.html.Node} node Node to replace the current node with.
  6995. * @return {tinymce.html.Node} The old node that got replaced.
  6996. */
  6997. replace: function(node) {
  6998. var self = this;
  6999. if (node.parent) {
  7000. node.remove();
  7001. }
  7002. self.insert(node, self);
  7003. self.remove();
  7004. return self;
  7005. },
  7006. /**
  7007. * Gets/sets or removes an attribute by name.
  7008. *
  7009. * @example
  7010. * someNode.attr("name", "value"); // Sets an attribute
  7011. * console.log(someNode.attr("name")); // Gets an attribute
  7012. * someNode.attr("name", null); // Removes an attribute
  7013. *
  7014. * @method attr
  7015. * @param {String} name Attribute name to set or get.
  7016. * @param {String} value Optional value to set.
  7017. * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
  7018. */
  7019. attr: function(name, value) {
  7020. var self = this, attrs, i, undef;
  7021. if (typeof name !== "string") {
  7022. for (i in name) {
  7023. self.attr(i, name[i]);
  7024. }
  7025. return self;
  7026. }
  7027. if ((attrs = self.attributes)) {
  7028. if (value !== undef) {
  7029. // Remove attribute
  7030. if (value === null) {
  7031. if (name in attrs.map) {
  7032. delete attrs.map[name];
  7033. i = attrs.length;
  7034. while (i--) {
  7035. if (attrs[i].name === name) {
  7036. attrs = attrs.splice(i, 1);
  7037. return self;
  7038. }
  7039. }
  7040. }
  7041. return self;
  7042. }
  7043. // Set attribute
  7044. if (name in attrs.map) {
  7045. // Set attribute
  7046. i = attrs.length;
  7047. while (i--) {
  7048. if (attrs[i].name === name) {
  7049. attrs[i].value = value;
  7050. break;
  7051. }
  7052. }
  7053. } else {
  7054. attrs.push({name: name, value: value});
  7055. }
  7056. attrs.map[name] = value;
  7057. return self;
  7058. } else {
  7059. return attrs.map[name];
  7060. }
  7061. }
  7062. },
  7063. /**
  7064. * Does a shallow clones the node into a new node. It will also exclude id attributes since
  7065. * there should only be one id per document.
  7066. *
  7067. * @example
  7068. * var clonedNode = node.clone();
  7069. *
  7070. * @method clone
  7071. * @return {tinymce.html.Node} New copy of the original node.
  7072. */
  7073. clone: function() {
  7074. var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
  7075. // Clone element attributes
  7076. if ((selfAttrs = self.attributes)) {
  7077. cloneAttrs = [];
  7078. cloneAttrs.map = {};
  7079. for (i = 0, l = selfAttrs.length; i < l; i++) {
  7080. selfAttr = selfAttrs[i];
  7081. // Clone everything except id
  7082. if (selfAttr.name !== 'id') {
  7083. cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
  7084. cloneAttrs.map[selfAttr.name] = selfAttr.value;
  7085. }
  7086. }
  7087. clone.attributes = cloneAttrs;
  7088. }
  7089. clone.value = self.value;
  7090. clone.shortEnded = self.shortEnded;
  7091. return clone;
  7092. },
  7093. /**
  7094. * Wraps the node in in another node.
  7095. *
  7096. * @example
  7097. * node.wrap(wrapperNode);
  7098. *
  7099. * @method wrap
  7100. */
  7101. wrap: function(wrapper) {
  7102. var self = this;
  7103. self.parent.insert(wrapper, self);
  7104. wrapper.append(self);
  7105. return self;
  7106. },
  7107. /**
  7108. * Unwraps the node in other words it removes the node but keeps the children.
  7109. *
  7110. * @example
  7111. * node.unwrap();
  7112. *
  7113. * @method unwrap
  7114. */
  7115. unwrap: function() {
  7116. var self = this, node, next;
  7117. for (node = self.firstChild; node; ) {
  7118. next = node.next;
  7119. self.insert(node, self, true);
  7120. node = next;
  7121. }
  7122. self.remove();
  7123. },
  7124. /**
  7125. * Removes the node from it's parent.
  7126. *
  7127. * @example
  7128. * node.remove();
  7129. *
  7130. * @method remove
  7131. * @return {tinymce.html.Node} Current node that got removed.
  7132. */
  7133. remove: function() {
  7134. var self = this, parent = self.parent, next = self.next, prev = self.prev;
  7135. if (parent) {
  7136. if (parent.firstChild === self) {
  7137. parent.firstChild = next;
  7138. if (next) {
  7139. next.prev = null;
  7140. }
  7141. } else {
  7142. prev.next = next;
  7143. }
  7144. if (parent.lastChild === self) {
  7145. parent.lastChild = prev;
  7146. if (prev) {
  7147. prev.next = null;
  7148. }
  7149. } else {
  7150. next.prev = prev;
  7151. }
  7152. self.parent = self.next = self.prev = null;
  7153. }
  7154. return self;
  7155. },
  7156. /**
  7157. * Appends a new node as a child of the current node.
  7158. *
  7159. * @example
  7160. * node.append(someNode);
  7161. *
  7162. * @method append
  7163. * @param {tinymce.html.Node} node Node to append as a child of the current one.
  7164. * @return {tinymce.html.Node} The node that got appended.
  7165. */
  7166. append: function(node) {
  7167. var self = this, last;
  7168. if (node.parent) {
  7169. node.remove();
  7170. }
  7171. last = self.lastChild;
  7172. if (last) {
  7173. last.next = node;
  7174. node.prev = last;
  7175. self.lastChild = node;
  7176. } else {
  7177. self.lastChild = self.firstChild = node;
  7178. }
  7179. node.parent = self;
  7180. return node;
  7181. },
  7182. /**
  7183. * Inserts a node at a specific position as a child of the current node.
  7184. *
  7185. * @example
  7186. * parentNode.insert(newChildNode, oldChildNode);
  7187. *
  7188. * @method insert
  7189. * @param {tinymce.html.Node} node Node to insert as a child of the current node.
  7190. * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
  7191. * @param {Boolean} before Optional state to insert the node before the reference node.
  7192. * @return {tinymce.html.Node} The node that got inserted.
  7193. */
  7194. insert: function(node, ref_node, before) {
  7195. var parent;
  7196. if (node.parent) {
  7197. node.remove();
  7198. }
  7199. parent = ref_node.parent || this;
  7200. if (before) {
  7201. if (ref_node === parent.firstChild) {
  7202. parent.firstChild = node;
  7203. } else {
  7204. ref_node.prev.next = node;
  7205. }
  7206. node.prev = ref_node.prev;
  7207. node.next = ref_node;
  7208. ref_node.prev = node;
  7209. } else {
  7210. if (ref_node === parent.lastChild) {
  7211. parent.lastChild = node;
  7212. } else {
  7213. ref_node.next.prev = node;
  7214. }
  7215. node.next = ref_node.next;
  7216. node.prev = ref_node;
  7217. ref_node.next = node;
  7218. }
  7219. node.parent = parent;
  7220. return node;
  7221. },
  7222. /**
  7223. * Get all children by name.
  7224. *
  7225. * @method getAll
  7226. * @param {String} name Name of the child nodes to collect.
  7227. * @return {Array} Array with child nodes matchin the specified name.
  7228. */
  7229. getAll: function(name) {
  7230. var self = this, node, collection = [];
  7231. for (node = self.firstChild; node; node = walk(node, self)) {
  7232. if (node.name === name) {
  7233. collection.push(node);
  7234. }
  7235. }
  7236. return collection;
  7237. },
  7238. /**
  7239. * Removes all children of the current node.
  7240. *
  7241. * @method empty
  7242. * @return {tinymce.html.Node} The current node that got cleared.
  7243. */
  7244. empty: function() {
  7245. var self = this, nodes, i, node;
  7246. // Remove all children
  7247. if (self.firstChild) {
  7248. nodes = [];
  7249. // Collect the children
  7250. for (node = self.firstChild; node; node = walk(node, self)) {
  7251. nodes.push(node);
  7252. }
  7253. // Remove the children
  7254. i = nodes.length;
  7255. while (i--) {
  7256. node = nodes[i];
  7257. node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
  7258. }
  7259. }
  7260. self.firstChild = self.lastChild = null;
  7261. return self;
  7262. },
  7263. /**
  7264. * Returns true/false if the node is to be considered empty or not.
  7265. *
  7266. * @example
  7267. * node.isEmpty({img: true});
  7268. * @method isEmpty
  7269. * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
  7270. * @return {Boolean} true/false if the node is empty or not.
  7271. */
  7272. isEmpty: function(elements) {
  7273. var self = this, node = self.firstChild, i, name;
  7274. if (node) {
  7275. do {
  7276. if (node.type === 1) {
  7277. // Ignore bogus elements
  7278. if (node.attributes.map['data-mce-bogus']) {
  7279. continue;
  7280. }
  7281. // Keep empty elements like <img />
  7282. if (elements[node.name]) {
  7283. return false;
  7284. }
  7285. // Keep elements with data attributes or name attribute like <a name="1"></a>
  7286. i = node.attributes.length;
  7287. while (i--) {
  7288. name = node.attributes[i].name;
  7289. if (name === "name" || name.indexOf('data-mce-') === 0) {
  7290. return false;
  7291. }
  7292. }
  7293. }
  7294. // Keep comments
  7295. if (node.type === 8) {
  7296. return false;
  7297. }
  7298. // Keep non whitespace text nodes
  7299. if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
  7300. return false;
  7301. }
  7302. } while ((node = walk(node, self)));
  7303. }
  7304. return true;
  7305. },
  7306. /**
  7307. * Walks to the next or previous node and returns that node or null if it wasn't found.
  7308. *
  7309. * @method walk
  7310. * @param {Boolean} prev Optional previous node state defaults to false.
  7311. * @return {tinymce.html.Node} Node that is next to or previous of the current node.
  7312. */
  7313. walk: function(prev) {
  7314. return walk(this, null, prev);
  7315. }
  7316. };
  7317. /**
  7318. * Creates a node of a specific type.
  7319. *
  7320. * @static
  7321. * @method create
  7322. * @param {String} name Name of the node type to create for example "b" or "#text".
  7323. * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
  7324. */
  7325. Node.create = function(name, attrs) {
  7326. var node, attrName;
  7327. // Create node
  7328. node = new Node(name, typeLookup[name] || 1);
  7329. // Add attributes if needed
  7330. if (attrs) {
  7331. for (attrName in attrs) {
  7332. node.attr(attrName, attrs[attrName]);
  7333. }
  7334. }
  7335. return node;
  7336. };
  7337. return Node;
  7338. });
  7339. // Included from: js/tinymce/classes/html/Schema.js
  7340. /**
  7341. * Schema.js
  7342. *
  7343. * Copyright, Moxiecode Systems AB
  7344. * Released under LGPL License.
  7345. *
  7346. * License: http://www.tinymce.com/license
  7347. * Contributing: http://www.tinymce.com/contributing
  7348. */
  7349. /**
  7350. * Schema validator class.
  7351. *
  7352. * @class tinymce.html.Schema
  7353. * @example
  7354. * if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
  7355. * alert('span is valid child of p.');
  7356. *
  7357. * if (tinymce.activeEditor.schema.getElementRule('p'))
  7358. * alert('P is a valid element.');
  7359. *
  7360. * @class tinymce.html.Schema
  7361. * @version 3.4
  7362. */
  7363. define("tinymce/html/Schema", [
  7364. "tinymce/util/Tools"
  7365. ], function(Tools) {
  7366. var mapCache = {};
  7367. var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
  7368. function split(items, delim) {
  7369. return items ? items.split(delim || ' ') : [];
  7370. }
  7371. /**
  7372. * Builds a schema lookup table
  7373. *
  7374. * @private
  7375. * @param {String} type html4, html5 or html5-strict schema type.
  7376. * @return {Object} Schema lookup table.
  7377. */
  7378. function compileSchema(type) {
  7379. var schema = {}, globalAttributes, blockContent;
  7380. var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
  7381. function add(name, attributes, children) {
  7382. var ni, i, attributesOrder, args = arguments;
  7383. function arrayToMap(array) {
  7384. var map = {}, i, l;
  7385. for (i = 0, l = array.length; i < l; i++) {
  7386. map[array[i]] = {};
  7387. }
  7388. return map;
  7389. }
  7390. children = children || [];
  7391. attributes = attributes || "";
  7392. if (typeof(children) === "string") {
  7393. children = split(children);
  7394. }
  7395. // Split string children
  7396. for (i = 3; i < args.length; i++) {
  7397. if (typeof(args[i]) === "string") {
  7398. args[i] = split(args[i]);
  7399. }
  7400. children.push.apply(children, args[i]);
  7401. }
  7402. name = split(name);
  7403. ni = name.length;
  7404. while (ni--) {
  7405. attributesOrder = [].concat(globalAttributes, split(attributes));
  7406. schema[name[ni]] = {
  7407. attributes: arrayToMap(attributesOrder),
  7408. attributesOrder: attributesOrder,
  7409. children: arrayToMap(children)
  7410. };
  7411. }
  7412. }
  7413. function addAttrs(name, attributes) {
  7414. var ni, schemaItem, i, l;
  7415. name = split(name);
  7416. ni = name.length;
  7417. attributes = split(attributes);
  7418. while (ni--) {
  7419. schemaItem = schema[name[ni]];
  7420. for (i = 0, l = attributes.length; i < l; i++) {
  7421. schemaItem.attributes[attributes[i]] = {};
  7422. schemaItem.attributesOrder.push(attributes[i]);
  7423. }
  7424. }
  7425. }
  7426. // Use cached schema
  7427. if (mapCache[type]) {
  7428. return mapCache[type];
  7429. }
  7430. // Attributes present on all elements
  7431. globalAttributes = split("id accesskey class dir lang style tabindex title");
  7432. // Event attributes can be opt-in/opt-out
  7433. /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
  7434. "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
  7435. "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
  7436. "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
  7437. "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
  7438. "onwaiting"
  7439. );*/
  7440. // Block content elements
  7441. blockContent = split(
  7442. "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
  7443. );
  7444. // Phrasing content elements from the HTML5 spec (inline)
  7445. phrasingContent = split(
  7446. "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
  7447. "label map noscript object q s samp script select small span strong sub sup " +
  7448. "textarea u var #text #comment"
  7449. );
  7450. // Add HTML5 items to globalAttributes, blockContent, phrasingContent
  7451. if (type != "html4") {
  7452. globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
  7453. "hidden spellcheck translate"));
  7454. blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
  7455. phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
  7456. "video ruby bdi keygen"));
  7457. }
  7458. // Add HTML4 elements unless it's html5-strict
  7459. if (type != "html5-strict") {
  7460. globalAttributes.push("xml:lang");
  7461. html4PhrasingContent = split("acronym applet basefont big font strike tt");
  7462. phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
  7463. each(html4PhrasingContent, function(name) {
  7464. add(name, "", phrasingContent);
  7465. });
  7466. html4BlockContent = split("center dir isindex noframes");
  7467. blockContent.push.apply(blockContent, html4BlockContent);
  7468. // Flow content elements from the HTML5 spec (block+inline)
  7469. flowContent = [].concat(blockContent, phrasingContent);
  7470. each(html4BlockContent, function(name) {
  7471. add(name, "", flowContent);
  7472. });
  7473. }
  7474. // Flow content elements from the HTML5 spec (block+inline)
  7475. flowContent = flowContent || [].concat(blockContent, phrasingContent);
  7476. // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
  7477. // Schema items <element name>, <specific attributes>, <children ..>
  7478. add("html", "manifest", "head body");
  7479. add("head", "", "base command link meta noscript script style title");
  7480. add("title hr noscript br");
  7481. add("base", "href target");
  7482. add("link", "href rel media hreflang type sizes hreflang");
  7483. add("meta", "name http-equiv content charset");
  7484. add("style", "media type scoped");
  7485. add("script", "src async defer type charset");
  7486. add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
  7487. "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
  7488. "onpopstate onresize onscroll onstorage onunload", flowContent);
  7489. add("address dt dd div caption", "", flowContent);
  7490. 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);
  7491. add("blockquote", "cite", flowContent);
  7492. add("ol", "reversed start type", "li");
  7493. add("ul", "", "li");
  7494. add("li", "value", flowContent);
  7495. add("dl", "", "dt dd");
  7496. add("a", "href target rel media hreflang type", phrasingContent);
  7497. add("q", "cite", phrasingContent);
  7498. add("ins del", "cite datetime", flowContent);
  7499. add("img", "src alt usemap ismap width height");
  7500. add("iframe", "src name width height", flowContent);
  7501. add("embed", "src type width height");
  7502. add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
  7503. add("param", "name value");
  7504. add("map", "name", flowContent, "area");
  7505. add("area", "alt coords shape href target rel media hreflang type");
  7506. add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
  7507. add("colgroup", "span", "col");
  7508. add("col", "span");
  7509. add("tbody thead tfoot", "", "tr");
  7510. add("tr", "", "td th");
  7511. add("td", "colspan rowspan headers", flowContent);
  7512. add("th", "colspan rowspan headers scope abbr", flowContent);
  7513. add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
  7514. add("fieldset", "disabled form name", flowContent, "legend");
  7515. add("label", "form for", phrasingContent);
  7516. add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
  7517. "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
  7518. );
  7519. add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
  7520. type == "html4" ? flowContent : phrasingContent);
  7521. add("select", "disabled form multiple name required size", "option optgroup");
  7522. add("optgroup", "disabled label", "option");
  7523. add("option", "disabled label selected value");
  7524. add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
  7525. add("menu", "type label", flowContent, "li");
  7526. add("noscript", "", flowContent);
  7527. // Extend with HTML5 elements
  7528. if (type != "html4") {
  7529. add("wbr");
  7530. add("ruby", "", phrasingContent, "rt rp");
  7531. add("figcaption", "", flowContent);
  7532. add("mark rt rp summary bdi", "", phrasingContent);
  7533. add("canvas", "width height", flowContent);
  7534. add("video", "src crossorigin poster preload autoplay mediagroup loop " +
  7535. "muted controls width height buffered", flowContent, "track source");
  7536. add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
  7537. add("source", "src type media");
  7538. add("track", "kind src srclang label default");
  7539. add("datalist", "", phrasingContent, "option");
  7540. add("article section nav aside header footer", "", flowContent);
  7541. add("hgroup", "", "h1 h2 h3 h4 h5 h6");
  7542. add("figure", "", flowContent, "figcaption");
  7543. add("time", "datetime", phrasingContent);
  7544. add("dialog", "open", flowContent);
  7545. add("command", "type label icon disabled checked radiogroup command");
  7546. add("output", "for form name", phrasingContent);
  7547. add("progress", "value max", phrasingContent);
  7548. add("meter", "value min max low high optimum", phrasingContent);
  7549. add("details", "open", flowContent, "summary");
  7550. add("keygen", "autofocus challenge disabled form keytype name");
  7551. }
  7552. // Extend with HTML4 attributes unless it's html5-strict
  7553. if (type != "html5-strict") {
  7554. addAttrs("script", "language xml:space");
  7555. addAttrs("style", "xml:space");
  7556. addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace");
  7557. addAttrs("param", "valuetype type");
  7558. addAttrs("a", "charset name rev shape coords");
  7559. addAttrs("br", "clear");
  7560. addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
  7561. addAttrs("img", "name longdesc align border hspace vspace");
  7562. addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
  7563. addAttrs("font basefont", "size color face");
  7564. addAttrs("input", "usemap align");
  7565. addAttrs("select", "onchange");
  7566. addAttrs("textarea");
  7567. addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
  7568. addAttrs("ul", "type compact");
  7569. addAttrs("li", "type");
  7570. addAttrs("ol dl menu dir", "compact");
  7571. addAttrs("pre", "width xml:space");
  7572. addAttrs("hr", "align noshade size width");
  7573. addAttrs("isindex", "prompt");
  7574. addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
  7575. addAttrs("col", "width align char charoff valign");
  7576. addAttrs("colgroup", "width align char charoff valign");
  7577. addAttrs("thead", "align char charoff valign");
  7578. addAttrs("tr", "align char charoff valign bgcolor");
  7579. addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
  7580. addAttrs("form", "accept");
  7581. addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
  7582. addAttrs("tfoot", "align char charoff valign");
  7583. addAttrs("tbody", "align char charoff valign");
  7584. addAttrs("area", "nohref");
  7585. addAttrs("body", "background bgcolor text link vlink alink");
  7586. }
  7587. // Extend with HTML5 attributes unless it's html4
  7588. if (type != "html4") {
  7589. addAttrs("input button select textarea", "autofocus");
  7590. addAttrs("input textarea", "placeholder");
  7591. addAttrs("a", "download");
  7592. addAttrs("link script img", "crossorigin");
  7593. addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
  7594. }
  7595. // Special: iframe, ruby, video, audio, label
  7596. // Delete children of the same name from it's parent
  7597. // For example: form can't have a child of the name form
  7598. each(split('a form meter progress dfn'), function(name) {
  7599. if (schema[name]) {
  7600. delete schema[name].children[name];
  7601. }
  7602. });
  7603. // Delete header, footer, sectioning and heading content descendants
  7604. /*each('dt th address', function(name) {
  7605. delete schema[name].children[name];
  7606. });*/
  7607. // Caption can't have tables
  7608. delete schema.caption.children.table;
  7609. // TODO: LI:s can only have value if parent is OL
  7610. // TODO: Handle transparent elements
  7611. // a ins del canvas map
  7612. mapCache[type] = schema;
  7613. return schema;
  7614. }
  7615. /**
  7616. * Constructs a new Schema instance.
  7617. *
  7618. * @constructor
  7619. * @method Schema
  7620. * @param {Object} settings Name/value settings object.
  7621. */
  7622. return function(settings) {
  7623. var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
  7624. var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap;
  7625. var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {};
  7626. // Creates an lookup table map object for the specified option or the default value
  7627. function createLookupTable(option, default_value, extendWith) {
  7628. var value = settings[option];
  7629. if (!value) {
  7630. // Get cached default map or make it if needed
  7631. value = mapCache[option];
  7632. if (!value) {
  7633. value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
  7634. value = extend(value, extendWith);
  7635. mapCache[option] = value;
  7636. }
  7637. } else {
  7638. // Create custom map
  7639. value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
  7640. }
  7641. return value;
  7642. }
  7643. settings = settings || {};
  7644. schemaItems = compileSchema(settings.schema);
  7645. // Allow all elements and attributes if verify_html is set to false
  7646. if (settings.verify_html === false) {
  7647. settings.valid_elements = '*[*]';
  7648. }
  7649. // Build styles list
  7650. if (settings.valid_styles) {
  7651. validStyles = {};
  7652. // Convert styles into a rule list
  7653. each(settings.valid_styles, function(value, key) {
  7654. validStyles[key] = explode(value);
  7655. });
  7656. }
  7657. // Setup map objects
  7658. whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
  7659. selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
  7660. shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
  7661. 'meta param embed source wbr track');
  7662. boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
  7663. 'noshade nowrap readonly selected autoplay loop controls');
  7664. nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
  7665. textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
  7666. 'blockquote center dir fieldset header footer article section hgroup aside nav figure');
  7667. blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
  7668. 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
  7669. 'datalist select optgroup', textBlockElementsMap);
  7670. each((settings.special || 'script noscript style textarea').split(' '), function(name) {
  7671. specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi');
  7672. });
  7673. // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
  7674. function patternToRegExp(str) {
  7675. return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
  7676. }
  7677. // Parses the specified valid_elements string and adds to the current rules
  7678. // This function is a bit hard to read since it's heavily optimized for speed
  7679. function addValidElements(valid_elements) {
  7680. var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
  7681. prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
  7682. elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
  7683. attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
  7684. hasPatternsRegExp = /[*?+]/;
  7685. if (valid_elements) {
  7686. // Split valid elements into an array with rules
  7687. valid_elements = split(valid_elements, ',');
  7688. if (elements['@']) {
  7689. globalAttributes = elements['@'].attributes;
  7690. globalAttributesOrder = elements['@'].attributesOrder;
  7691. }
  7692. // Loop all rules
  7693. for (ei = 0, el = valid_elements.length; ei < el; ei++) {
  7694. // Parse element rule
  7695. matches = elementRuleRegExp.exec(valid_elements[ei]);
  7696. if (matches) {
  7697. // Setup local names for matches
  7698. prefix = matches[1];
  7699. elementName = matches[2];
  7700. outputName = matches[3];
  7701. attrData = matches[5];
  7702. // Create new attributes and attributesOrder
  7703. attributes = {};
  7704. attributesOrder = [];
  7705. // Create the new element
  7706. element = {
  7707. attributes: attributes,
  7708. attributesOrder: attributesOrder
  7709. };
  7710. // Padd empty elements prefix
  7711. if (prefix === '#') {
  7712. element.paddEmpty = true;
  7713. }
  7714. // Remove empty elements prefix
  7715. if (prefix === '-') {
  7716. element.removeEmpty = true;
  7717. }
  7718. if (matches[4] === '!') {
  7719. element.removeEmptyAttrs = true;
  7720. }
  7721. // Copy attributes from global rule into current rule
  7722. if (globalAttributes) {
  7723. for (key in globalAttributes) {
  7724. attributes[key] = globalAttributes[key];
  7725. }
  7726. attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
  7727. }
  7728. // Attributes defined
  7729. if (attrData) {
  7730. attrData = split(attrData, '|');
  7731. for (ai = 0, al = attrData.length; ai < al; ai++) {
  7732. matches = attrRuleRegExp.exec(attrData[ai]);
  7733. if (matches) {
  7734. attr = {};
  7735. attrType = matches[1];
  7736. attrName = matches[2].replace(/::/g, ':');
  7737. prefix = matches[3];
  7738. value = matches[4];
  7739. // Required
  7740. if (attrType === '!') {
  7741. element.attributesRequired = element.attributesRequired || [];
  7742. element.attributesRequired.push(attrName);
  7743. attr.required = true;
  7744. }
  7745. // Denied from global
  7746. if (attrType === '-') {
  7747. delete attributes[attrName];
  7748. attributesOrder.splice(inArray(attributesOrder, attrName), 1);
  7749. continue;
  7750. }
  7751. // Default value
  7752. if (prefix) {
  7753. // Default value
  7754. if (prefix === '=') {
  7755. element.attributesDefault = element.attributesDefault || [];
  7756. element.attributesDefault.push({name: attrName, value: value});
  7757. attr.defaultValue = value;
  7758. }
  7759. // Forced value
  7760. if (prefix === ':') {
  7761. element.attributesForced = element.attributesForced || [];
  7762. element.attributesForced.push({name: attrName, value: value});
  7763. attr.forcedValue = value;
  7764. }
  7765. // Required values
  7766. if (prefix === '<') {
  7767. attr.validValues = makeMap(value, '?');
  7768. }
  7769. }
  7770. // Check for attribute patterns
  7771. if (hasPatternsRegExp.test(attrName)) {
  7772. element.attributePatterns = element.attributePatterns || [];
  7773. attr.pattern = patternToRegExp(attrName);
  7774. element.attributePatterns.push(attr);
  7775. } else {
  7776. // Add attribute to order list if it doesn't already exist
  7777. if (!attributes[attrName]) {
  7778. attributesOrder.push(attrName);
  7779. }
  7780. attributes[attrName] = attr;
  7781. }
  7782. }
  7783. }
  7784. }
  7785. // Global rule, store away these for later usage
  7786. if (!globalAttributes && elementName == '@') {
  7787. globalAttributes = attributes;
  7788. globalAttributesOrder = attributesOrder;
  7789. }
  7790. // Handle substitute elements such as b/strong
  7791. if (outputName) {
  7792. element.outputName = elementName;
  7793. elements[outputName] = element;
  7794. }
  7795. // Add pattern or exact element
  7796. if (hasPatternsRegExp.test(elementName)) {
  7797. element.pattern = patternToRegExp(elementName);
  7798. patternElements.push(element);
  7799. } else {
  7800. elements[elementName] = element;
  7801. }
  7802. }
  7803. }
  7804. }
  7805. }
  7806. function setValidElements(valid_elements) {
  7807. elements = {};
  7808. patternElements = [];
  7809. addValidElements(valid_elements);
  7810. each(schemaItems, function(element, name) {
  7811. children[name] = element.children;
  7812. });
  7813. }
  7814. // Adds custom non HTML elements to the schema
  7815. function addCustomElements(custom_elements) {
  7816. var customElementRegExp = /^(~)?(.+)$/;
  7817. if (custom_elements) {
  7818. // Flush cached items since we are altering the default maps
  7819. mapCache.text_block_elements = mapCache.block_elements = null;
  7820. each(split(custom_elements, ','), function(rule) {
  7821. var matches = customElementRegExp.exec(rule),
  7822. inline = matches[1] === '~',
  7823. cloneName = inline ? 'span' : 'div',
  7824. name = matches[2];
  7825. children[name] = children[cloneName];
  7826. customElementsMap[name] = cloneName;
  7827. // If it's not marked as inline then add it to valid block elements
  7828. if (!inline) {
  7829. blockElementsMap[name.toUpperCase()] = {};
  7830. blockElementsMap[name] = {};
  7831. }
  7832. // Add elements clone if needed
  7833. if (!elements[name]) {
  7834. var customRule = elements[cloneName];
  7835. customRule = extend({}, customRule);
  7836. delete customRule.removeEmptyAttrs;
  7837. delete customRule.removeEmpty;
  7838. elements[name] = customRule;
  7839. }
  7840. // Add custom elements at span/div positions
  7841. each(children, function(element, elmName) {
  7842. if (element[cloneName]) {
  7843. children[elmName] = element = extend({}, children[elmName]);
  7844. element[name] = element[cloneName];
  7845. }
  7846. });
  7847. });
  7848. }
  7849. }
  7850. // Adds valid children to the schema object
  7851. function addValidChildren(valid_children) {
  7852. var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
  7853. if (valid_children) {
  7854. each(split(valid_children, ','), function(rule) {
  7855. var matches = childRuleRegExp.exec(rule), parent, prefix;
  7856. if (matches) {
  7857. prefix = matches[1];
  7858. // Add/remove items from default
  7859. if (prefix) {
  7860. parent = children[matches[2]];
  7861. } else {
  7862. parent = children[matches[2]] = {'#comment': {}};
  7863. }
  7864. parent = children[matches[2]];
  7865. each(split(matches[3], '|'), function(child) {
  7866. if (prefix === '-') {
  7867. // Clone the element before we delete
  7868. // things in it to not mess up default schemas
  7869. children[matches[2]] = parent = extend({}, children[matches[2]]);
  7870. delete parent[child];
  7871. } else {
  7872. parent[child] = {};
  7873. }
  7874. });
  7875. }
  7876. });
  7877. }
  7878. }
  7879. function getElementRule(name) {
  7880. var element = elements[name], i;
  7881. // Exact match found
  7882. if (element) {
  7883. return element;
  7884. }
  7885. // No exact match then try the patterns
  7886. i = patternElements.length;
  7887. while (i--) {
  7888. element = patternElements[i];
  7889. if (element.pattern.test(name)) {
  7890. return element;
  7891. }
  7892. }
  7893. }
  7894. if (!settings.valid_elements) {
  7895. // No valid elements defined then clone the elements from the schema spec
  7896. each(schemaItems, function(element, name) {
  7897. elements[name] = {
  7898. attributes: element.attributes,
  7899. attributesOrder: element.attributesOrder
  7900. };
  7901. children[name] = element.children;
  7902. });
  7903. // Switch these on HTML4
  7904. if (settings.schema != "html5") {
  7905. each(split('strong/b em/i'), function(item) {
  7906. item = split(item, '/');
  7907. elements[item[1]].outputName = item[0];
  7908. });
  7909. }
  7910. // Add default alt attribute for images
  7911. elements.img.attributesDefault = [{name: 'alt', value: ''}];
  7912. // Remove these if they are empty by default
  7913. each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
  7914. if (elements[name]) {
  7915. elements[name].removeEmpty = true;
  7916. }
  7917. });
  7918. // Padd these by default
  7919. each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
  7920. elements[name].paddEmpty = true;
  7921. });
  7922. // Remove these if they have no attributes
  7923. each(split('span'), function(name) {
  7924. elements[name].removeEmptyAttrs = true;
  7925. });
  7926. // Remove these by default
  7927. // TODO: Reenable in 4.1
  7928. /*each(split('script style'), function(name) {
  7929. delete elements[name];
  7930. });*/
  7931. } else {
  7932. setValidElements(settings.valid_elements);
  7933. }
  7934. addCustomElements(settings.custom_elements);
  7935. addValidChildren(settings.valid_children);
  7936. addValidElements(settings.extended_valid_elements);
  7937. // Todo: Remove this when we fix list handling to be valid
  7938. addValidChildren('+ol[ul|ol],+ul[ul|ol]');
  7939. // Delete invalid elements
  7940. if (settings.invalid_elements) {
  7941. each(explode(settings.invalid_elements), function(item) {
  7942. if (elements[item]) {
  7943. delete elements[item];
  7944. }
  7945. });
  7946. }
  7947. // If the user didn't allow span only allow internal spans
  7948. if (!getElementRule('span')) {
  7949. addValidElements('span[!data-mce-type|*]');
  7950. }
  7951. /**
  7952. * Name/value map object with valid parents and children to those parents.
  7953. *
  7954. * @example
  7955. * children = {
  7956. * div:{p:{}, h1:{}}
  7957. * };
  7958. * @field children
  7959. * @type Object
  7960. */
  7961. self.children = children;
  7962. /**
  7963. * Name/value map object with valid styles for each element.
  7964. *
  7965. * @field styles
  7966. * @type Object
  7967. */
  7968. self.styles = validStyles;
  7969. /**
  7970. * Returns a map with boolean attributes.
  7971. *
  7972. * @method getBoolAttrs
  7973. * @return {Object} Name/value lookup map for boolean attributes.
  7974. */
  7975. self.getBoolAttrs = function() {
  7976. return boolAttrMap;
  7977. };
  7978. /**
  7979. * Returns a map with block elements.
  7980. *
  7981. * @method getBlockElements
  7982. * @return {Object} Name/value lookup map for block elements.
  7983. */
  7984. self.getBlockElements = function() {
  7985. return blockElementsMap;
  7986. };
  7987. /**
  7988. * Returns a map with text block elements. Such as: p,h1-h6,div,address
  7989. *
  7990. * @method getTextBlockElements
  7991. * @return {Object} Name/value lookup map for block elements.
  7992. */
  7993. self.getTextBlockElements = function() {
  7994. return textBlockElementsMap;
  7995. };
  7996. /**
  7997. * Returns a map with short ended elements such as BR or IMG.
  7998. *
  7999. * @method getShortEndedElements
  8000. * @return {Object} Name/value lookup map for short ended elements.
  8001. */
  8002. self.getShortEndedElements = function() {
  8003. return shortEndedElementsMap;
  8004. };
  8005. /**
  8006. * Returns a map with self closing tags such as <li>.
  8007. *
  8008. * @method getSelfClosingElements
  8009. * @return {Object} Name/value lookup map for self closing tags elements.
  8010. */
  8011. self.getSelfClosingElements = function() {
  8012. return selfClosingElementsMap;
  8013. };
  8014. /**
  8015. * Returns a map with elements that should be treated as contents regardless if it has text
  8016. * content in them or not such as TD, VIDEO or IMG.
  8017. *
  8018. * @method getNonEmptyElements
  8019. * @return {Object} Name/value lookup map for non empty elements.
  8020. */
  8021. self.getNonEmptyElements = function() {
  8022. return nonEmptyElementsMap;
  8023. };
  8024. /**
  8025. * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
  8026. *
  8027. * @method getWhiteSpaceElements
  8028. * @return {Object} Name/value lookup map for white space elements.
  8029. */
  8030. self.getWhiteSpaceElements = function() {
  8031. return whiteSpaceElementsMap;
  8032. };
  8033. /**
  8034. * Returns a map with special elements. These are elements that needs to be parsed
  8035. * in a special way such as script, style, textarea etc. The map object values
  8036. * are regexps used to find the end of the element.
  8037. *
  8038. * @method getSpecialElements
  8039. * @return {Object} Name/value lookup map for special elements.
  8040. */
  8041. self.getSpecialElements = function() {
  8042. return specialElements;
  8043. };
  8044. /**
  8045. * Returns true/false if the specified element and it's child is valid or not
  8046. * according to the schema.
  8047. *
  8048. * @method isValidChild
  8049. * @param {String} name Element name to check for.
  8050. * @param {String} child Element child to verify.
  8051. * @return {Boolean} True/false if the element is a valid child of the specified parent.
  8052. */
  8053. self.isValidChild = function(name, child) {
  8054. var parent = children[name];
  8055. return !!(parent && parent[child]);
  8056. };
  8057. /**
  8058. * Returns true/false if the specified element name and optional attribute is
  8059. * valid according to the schema.
  8060. *
  8061. * @method isValid
  8062. * @param {String} name Name of element to check.
  8063. * @param {String} attr Optional attribute name to check for.
  8064. * @return {Boolean} True/false if the element and attribute is valid.
  8065. */
  8066. self.isValid = function(name, attr) {
  8067. var attrPatterns, i, rule = getElementRule(name);
  8068. // Check if it's a valid element
  8069. if (rule) {
  8070. if (attr) {
  8071. // Check if attribute name exists
  8072. if (rule.attributes[attr]) {
  8073. return true;
  8074. }
  8075. // Check if attribute matches a regexp pattern
  8076. attrPatterns = rule.attributePatterns;
  8077. if (attrPatterns) {
  8078. i = attrPatterns.length;
  8079. while (i--) {
  8080. if (attrPatterns[i].pattern.test(name)) {
  8081. return true;
  8082. }
  8083. }
  8084. }
  8085. } else {
  8086. return true;
  8087. }
  8088. }
  8089. // No match
  8090. return false;
  8091. };
  8092. /**
  8093. * Returns true/false if the specified element is valid or not
  8094. * according to the schema.
  8095. *
  8096. * @method getElementRule
  8097. * @param {String} name Element name to check for.
  8098. * @return {Object} Element object or undefined if the element isn't valid.
  8099. */
  8100. self.getElementRule = getElementRule;
  8101. /**
  8102. * Returns an map object of all custom elements.
  8103. *
  8104. * @method getCustomElements
  8105. * @return {Object} Name/value map object of all custom elements.
  8106. */
  8107. self.getCustomElements = function() {
  8108. return customElementsMap;
  8109. };
  8110. /**
  8111. * Parses a valid elements string and adds it to the schema. The valid elements
  8112. * format is for example "element[attr=default|otherattr]".
  8113. * Existing rules will be replaced with the ones specified, so this extends the schema.
  8114. *
  8115. * @method addValidElements
  8116. * @param {String} valid_elements String in the valid elements format to be parsed.
  8117. */
  8118. self.addValidElements = addValidElements;
  8119. /**
  8120. * Parses a valid elements string and sets it to the schema. The valid elements
  8121. * format is for example "element[attr=default|otherattr]".
  8122. * Existing rules will be replaced with the ones specified, so this extends the schema.
  8123. *
  8124. * @method setValidElements
  8125. * @param {String} valid_elements String in the valid elements format to be parsed.
  8126. */
  8127. self.setValidElements = setValidElements;
  8128. /**
  8129. * Adds custom non HTML elements to the schema.
  8130. *
  8131. * @method addCustomElements
  8132. * @param {String} custom_elements Comma separated list of custom elements to add.
  8133. */
  8134. self.addCustomElements = addCustomElements;
  8135. /**
  8136. * Parses a valid children string and adds them to the schema structure. The valid children
  8137. * format is for example: "element[child1|child2]".
  8138. *
  8139. * @method addValidChildren
  8140. * @param {String} valid_children Valid children elements string to parse
  8141. */
  8142. self.addValidChildren = addValidChildren;
  8143. self.elements = elements;
  8144. };
  8145. });
  8146. // Included from: js/tinymce/classes/html/SaxParser.js
  8147. /**
  8148. * SaxParser.js
  8149. *
  8150. * Copyright, Moxiecode Systems AB
  8151. * Released under LGPL License.
  8152. *
  8153. * License: http://www.tinymce.com/license
  8154. * Contributing: http://www.tinymce.com/contributing
  8155. */
  8156. /*eslint max-depth:[2, 9] */
  8157. /**
  8158. * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
  8159. * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
  8160. * and attributes that doesn't fit the schema if the validate setting is enabled.
  8161. *
  8162. * @example
  8163. * var parser = new tinymce.html.SaxParser({
  8164. * validate: true,
  8165. *
  8166. * comment: function(text) {
  8167. * console.log('Comment:', text);
  8168. * },
  8169. *
  8170. * cdata: function(text) {
  8171. * console.log('CDATA:', text);
  8172. * },
  8173. *
  8174. * text: function(text, raw) {
  8175. * console.log('Text:', text, 'Raw:', raw);
  8176. * },
  8177. *
  8178. * start: function(name, attrs, empty) {
  8179. * console.log('Start:', name, attrs, empty);
  8180. * },
  8181. *
  8182. * end: function(name) {
  8183. * console.log('End:', name);
  8184. * },
  8185. *
  8186. * pi: function(name, text) {
  8187. * console.log('PI:', name, text);
  8188. * },
  8189. *
  8190. * doctype: function(text) {
  8191. * console.log('DocType:', text);
  8192. * }
  8193. * }, schema);
  8194. * @class tinymce.html.SaxParser
  8195. * @version 3.4
  8196. */
  8197. define("tinymce/html/SaxParser", [
  8198. "tinymce/html/Schema",
  8199. "tinymce/html/Entities",
  8200. "tinymce/util/Tools"
  8201. ], function(Schema, Entities, Tools) {
  8202. var each = Tools.each;
  8203. /**
  8204. * Constructs a new SaxParser instance.
  8205. *
  8206. * @constructor
  8207. * @method SaxParser
  8208. * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  8209. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
  8210. */
  8211. return function(settings, schema) {
  8212. var self = this;
  8213. function noop() {}
  8214. settings = settings || {};
  8215. self.schema = schema = schema || new Schema();
  8216. if (settings.fix_self_closing !== false) {
  8217. settings.fix_self_closing = true;
  8218. }
  8219. // Add handler functions from settings and setup default handlers
  8220. each('comment cdata text start end pi doctype'.split(' '), function(name) {
  8221. if (name) {
  8222. self[name] = settings[name] || noop;
  8223. }
  8224. });
  8225. /**
  8226. * Parses the specified HTML string and executes the callbacks for each item it finds.
  8227. *
  8228. * @example
  8229. * new SaxParser({...}).parse('<b>text</b>');
  8230. * @method parse
  8231. * @param {String} html Html string to sax parse.
  8232. */
  8233. self.parse = function(html) {
  8234. var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
  8235. var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
  8236. var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
  8237. var attributesRequired, attributesDefault, attributesForced;
  8238. var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
  8239. var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
  8240. var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
  8241. function processEndTag(name) {
  8242. var pos, i;
  8243. // Find position of parent of the same type
  8244. pos = stack.length;
  8245. while (pos--) {
  8246. if (stack[pos].name === name) {
  8247. break;
  8248. }
  8249. }
  8250. // Found parent
  8251. if (pos >= 0) {
  8252. // Close all the open elements
  8253. for (i = stack.length - 1; i >= pos; i--) {
  8254. name = stack[i];
  8255. if (name.valid) {
  8256. self.end(name.name);
  8257. }
  8258. }
  8259. // Remove the open elements from the stack
  8260. stack.length = pos;
  8261. }
  8262. }
  8263. function parseAttribute(match, name, value, val2, val3) {
  8264. var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
  8265. name = name.toLowerCase();
  8266. value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
  8267. // Validate name and value pass through all data- attributes
  8268. if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
  8269. attrRule = validAttributesMap[name];
  8270. // Find rule by pattern matching
  8271. if (!attrRule && validAttributePatterns) {
  8272. i = validAttributePatterns.length;
  8273. while (i--) {
  8274. attrRule = validAttributePatterns[i];
  8275. if (attrRule.pattern.test(name)) {
  8276. break;
  8277. }
  8278. }
  8279. // No rule matched
  8280. if (i === -1) {
  8281. attrRule = null;
  8282. }
  8283. }
  8284. // No attribute rule found
  8285. if (!attrRule) {
  8286. return;
  8287. }
  8288. // Validate value
  8289. if (attrRule.validValues && !(value in attrRule.validValues)) {
  8290. return;
  8291. }
  8292. }
  8293. // Block any javascript: urls or non image data uris
  8294. if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
  8295. var uri = value.replace(trimRegExp, '');
  8296. try {
  8297. // Might throw malformed URI sequence
  8298. uri = decodeURIComponent(uri);
  8299. } catch (ex) {
  8300. // Fallback to non UTF-8 decoder
  8301. uri = unescape(uri);
  8302. }
  8303. if (scriptUriRegExp.test(uri)) {
  8304. return;
  8305. }
  8306. if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
  8307. return;
  8308. }
  8309. }
  8310. // Add attribute to list and map
  8311. attrList.map[name] = value;
  8312. attrList.push({
  8313. name: name,
  8314. value: value
  8315. });
  8316. }
  8317. // Precompile RegExps and map objects
  8318. tokenRegExp = new RegExp('<(?:' +
  8319. '(?:!--([\\w\\W]*?)-->)|' + // Comment
  8320. '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
  8321. '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
  8322. '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
  8323. '(?:\\/([^>]+)>)|' + // End element
  8324. '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
  8325. ')', 'g');
  8326. attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
  8327. // Setup lookup tables for empty elements and boolean attributes
  8328. shortEndedElements = schema.getShortEndedElements();
  8329. selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
  8330. fillAttrsMap = schema.getBoolAttrs();
  8331. validate = settings.validate;
  8332. removeInternalElements = settings.remove_internals;
  8333. fixSelfClosing = settings.fix_self_closing;
  8334. specialElements = schema.getSpecialElements();
  8335. while ((matches = tokenRegExp.exec(html))) {
  8336. // Text
  8337. if (index < matches.index) {
  8338. self.text(decode(html.substr(index, matches.index - index)));
  8339. }
  8340. if ((value = matches[6])) { // End element
  8341. value = value.toLowerCase();
  8342. // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  8343. if (value.charAt(0) === ':') {
  8344. value = value.substr(1);
  8345. }
  8346. processEndTag(value);
  8347. } else if ((value = matches[7])) { // Start element
  8348. value = value.toLowerCase();
  8349. // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  8350. if (value.charAt(0) === ':') {
  8351. value = value.substr(1);
  8352. }
  8353. isShortEnded = value in shortEndedElements;
  8354. // Is self closing tag for example an <li> after an open <li>
  8355. if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
  8356. processEndTag(value);
  8357. }
  8358. // Validate element
  8359. if (!validate || (elementRule = schema.getElementRule(value))) {
  8360. isValidElement = true;
  8361. // Grab attributes map and patters when validation is enabled
  8362. if (validate) {
  8363. validAttributesMap = elementRule.attributes;
  8364. validAttributePatterns = elementRule.attributePatterns;
  8365. }
  8366. // Parse attributes
  8367. if ((attribsValue = matches[8])) {
  8368. isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
  8369. // If the element has internal attributes then remove it if we are told to do so
  8370. if (isInternalElement && removeInternalElements) {
  8371. isValidElement = false;
  8372. }
  8373. attrList = [];
  8374. attrList.map = {};
  8375. attribsValue.replace(attrRegExp, parseAttribute);
  8376. } else {
  8377. attrList = [];
  8378. attrList.map = {};
  8379. }
  8380. // Process attributes if validation is enabled
  8381. if (validate && !isInternalElement) {
  8382. attributesRequired = elementRule.attributesRequired;
  8383. attributesDefault = elementRule.attributesDefault;
  8384. attributesForced = elementRule.attributesForced;
  8385. anyAttributesRequired = elementRule.removeEmptyAttrs;
  8386. // Check if any attribute exists
  8387. if (anyAttributesRequired && !attrList.length) {
  8388. isValidElement = false;
  8389. }
  8390. // Handle forced attributes
  8391. if (attributesForced) {
  8392. i = attributesForced.length;
  8393. while (i--) {
  8394. attr = attributesForced[i];
  8395. name = attr.name;
  8396. attrValue = attr.value;
  8397. if (attrValue === '{$uid}') {
  8398. attrValue = 'mce_' + idCount++;
  8399. }
  8400. attrList.map[name] = attrValue;
  8401. attrList.push({name: name, value: attrValue});
  8402. }
  8403. }
  8404. // Handle default attributes
  8405. if (attributesDefault) {
  8406. i = attributesDefault.length;
  8407. while (i--) {
  8408. attr = attributesDefault[i];
  8409. name = attr.name;
  8410. if (!(name in attrList.map)) {
  8411. attrValue = attr.value;
  8412. if (attrValue === '{$uid}') {
  8413. attrValue = 'mce_' + idCount++;
  8414. }
  8415. attrList.map[name] = attrValue;
  8416. attrList.push({name: name, value: attrValue});
  8417. }
  8418. }
  8419. }
  8420. // Handle required attributes
  8421. if (attributesRequired) {
  8422. i = attributesRequired.length;
  8423. while (i--) {
  8424. if (attributesRequired[i] in attrList.map) {
  8425. break;
  8426. }
  8427. }
  8428. // None of the required attributes where found
  8429. if (i === -1) {
  8430. isValidElement = false;
  8431. }
  8432. }
  8433. // Invalidate element if it's marked as bogus
  8434. if (attrList.map['data-mce-bogus']) {
  8435. isValidElement = false;
  8436. }
  8437. }
  8438. if (isValidElement) {
  8439. self.start(value, attrList, isShortEnded);
  8440. }
  8441. } else {
  8442. isValidElement = false;
  8443. }
  8444. // Treat script, noscript and style a bit different since they may include code that looks like elements
  8445. if ((endRegExp = specialElements[value])) {
  8446. endRegExp.lastIndex = index = matches.index + matches[0].length;
  8447. if ((matches = endRegExp.exec(html))) {
  8448. if (isValidElement) {
  8449. text = html.substr(index, matches.index - index);
  8450. }
  8451. index = matches.index + matches[0].length;
  8452. } else {
  8453. text = html.substr(index);
  8454. index = html.length;
  8455. }
  8456. if (isValidElement) {
  8457. if (text.length > 0) {
  8458. self.text(text, true);
  8459. }
  8460. self.end(value);
  8461. }
  8462. tokenRegExp.lastIndex = index;
  8463. continue;
  8464. }
  8465. // Push value on to stack
  8466. if (!isShortEnded) {
  8467. if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
  8468. stack.push({name: value, valid: isValidElement});
  8469. } else if (isValidElement) {
  8470. self.end(value);
  8471. }
  8472. }
  8473. } else if ((value = matches[1])) { // Comment
  8474. // Padd comment value to avoid browsers from parsing invalid comments as HTML
  8475. if (value.charAt(0) === '>') {
  8476. value = ' ' + value;
  8477. }
  8478. if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
  8479. value = ' ' + value;
  8480. }
  8481. self.comment(value);
  8482. } else if ((value = matches[2])) { // CDATA
  8483. self.cdata(value);
  8484. } else if ((value = matches[3])) { // DOCTYPE
  8485. self.doctype(value);
  8486. } else if ((value = matches[4])) { // PI
  8487. self.pi(value, matches[5]);
  8488. }
  8489. index = matches.index + matches[0].length;
  8490. }
  8491. // Text
  8492. if (index < html.length) {
  8493. self.text(decode(html.substr(index)));
  8494. }
  8495. // Close any open elements
  8496. for (i = stack.length - 1; i >= 0; i--) {
  8497. value = stack[i];
  8498. if (value.valid) {
  8499. self.end(value.name);
  8500. }
  8501. }
  8502. };
  8503. };
  8504. });
  8505. // Included from: js/tinymce/classes/html/DomParser.js
  8506. /**
  8507. * DomParser.js
  8508. *
  8509. * Copyright, Moxiecode Systems AB
  8510. * Released under LGPL License.
  8511. *
  8512. * License: http://www.tinymce.com/license
  8513. * Contributing: http://www.tinymce.com/contributing
  8514. */
  8515. /**
  8516. * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
  8517. * sure that the node tree is valid according to the specified schema.
  8518. * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
  8519. *
  8520. * @example
  8521. * var parser = new tinymce.html.DomParser({validate: true}, schema);
  8522. * var rootNode = parser.parse('<h1>content</h1>');
  8523. *
  8524. * @class tinymce.html.DomParser
  8525. * @version 3.4
  8526. */
  8527. define("tinymce/html/DomParser", [
  8528. "tinymce/html/Node",
  8529. "tinymce/html/Schema",
  8530. "tinymce/html/SaxParser",
  8531. "tinymce/util/Tools"
  8532. ], function(Node, Schema, SaxParser, Tools) {
  8533. var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
  8534. /**
  8535. * Constructs a new DomParser instance.
  8536. *
  8537. * @constructor
  8538. * @method DomParser
  8539. * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  8540. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
  8541. */
  8542. return function(settings, schema) {
  8543. var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
  8544. settings = settings || {};
  8545. settings.validate = "validate" in settings ? settings.validate : true;
  8546. settings.root_name = settings.root_name || 'body';
  8547. self.schema = schema = schema || new Schema();
  8548. function fixInvalidChildren(nodes) {
  8549. var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
  8550. var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
  8551. nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
  8552. nonEmptyElements = schema.getNonEmptyElements();
  8553. textBlockElements = schema.getTextBlockElements();
  8554. for (ni = 0; ni < nodes.length; ni++) {
  8555. node = nodes[ni];
  8556. // Already removed or fixed
  8557. if (!node.parent || node.fixed) {
  8558. continue;
  8559. }
  8560. // If the invalid element is a text block and the text block is within a parent LI element
  8561. // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
  8562. if (textBlockElements[node.name] && node.parent.name == 'li') {
  8563. // Move sibling text blocks after LI element
  8564. sibling = node.next;
  8565. while (sibling) {
  8566. if (textBlockElements[sibling.name]) {
  8567. sibling.name = 'li';
  8568. sibling.fixed = true;
  8569. node.parent.insert(sibling, node.parent);
  8570. } else {
  8571. break;
  8572. }
  8573. sibling = sibling.next;
  8574. }
  8575. // Unwrap current text block
  8576. node.unwrap(node);
  8577. continue;
  8578. }
  8579. // Get list of all parent nodes until we find a valid parent to stick the child into
  8580. parents = [node];
  8581. for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
  8582. !nonSplitableElements[parent.name]; parent = parent.parent) {
  8583. parents.push(parent);
  8584. }
  8585. // Found a suitable parent
  8586. if (parent && parents.length > 1) {
  8587. // Reverse the array since it makes looping easier
  8588. parents.reverse();
  8589. // Clone the related parent and insert that after the moved node
  8590. newParent = currentNode = self.filterNode(parents[0].clone());
  8591. // Start cloning and moving children on the left side of the target node
  8592. for (i = 0; i < parents.length - 1; i++) {
  8593. if (schema.isValidChild(currentNode.name, parents[i].name)) {
  8594. tempNode = self.filterNode(parents[i].clone());
  8595. currentNode.append(tempNode);
  8596. } else {
  8597. tempNode = currentNode;
  8598. }
  8599. for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
  8600. nextNode = childNode.next;
  8601. tempNode.append(childNode);
  8602. childNode = nextNode;
  8603. }
  8604. currentNode = tempNode;
  8605. }
  8606. if (!newParent.isEmpty(nonEmptyElements)) {
  8607. parent.insert(newParent, parents[0], true);
  8608. parent.insert(node, newParent);
  8609. } else {
  8610. parent.insert(node, parents[0], true);
  8611. }
  8612. // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
  8613. parent = parents[0];
  8614. if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
  8615. parent.empty().remove();
  8616. }
  8617. } else if (node.parent) {
  8618. // If it's an LI try to find a UL/OL for it or wrap it
  8619. if (node.name === 'li') {
  8620. sibling = node.prev;
  8621. if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  8622. sibling.append(node);
  8623. continue;
  8624. }
  8625. sibling = node.next;
  8626. if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  8627. sibling.insert(node, sibling.firstChild, true);
  8628. continue;
  8629. }
  8630. node.wrap(self.filterNode(new Node('ul', 1)));
  8631. continue;
  8632. }
  8633. // Try wrapping the element in a DIV
  8634. if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
  8635. node.wrap(self.filterNode(new Node('div', 1)));
  8636. } else {
  8637. // We failed wrapping it, then remove or unwrap it
  8638. if (node.name === 'style' || node.name === 'script') {
  8639. node.empty().remove();
  8640. } else {
  8641. node.unwrap();
  8642. }
  8643. }
  8644. }
  8645. }
  8646. }
  8647. /**
  8648. * Runs the specified node though the element and attributes filters.
  8649. *
  8650. * @method filterNode
  8651. * @param {tinymce.html.Node} Node the node to run filters on.
  8652. * @return {tinymce.html.Node} The passed in node.
  8653. */
  8654. self.filterNode = function(node) {
  8655. var i, name, list;
  8656. // Run element filters
  8657. if (name in nodeFilters) {
  8658. list = matchedNodes[name];
  8659. if (list) {
  8660. list.push(node);
  8661. } else {
  8662. matchedNodes[name] = [node];
  8663. }
  8664. }
  8665. // Run attribute filters
  8666. i = attributeFilters.length;
  8667. while (i--) {
  8668. name = attributeFilters[i].name;
  8669. if (name in node.attributes.map) {
  8670. list = matchedAttributes[name];
  8671. if (list) {
  8672. list.push(node);
  8673. } else {
  8674. matchedAttributes[name] = [node];
  8675. }
  8676. }
  8677. }
  8678. return node;
  8679. };
  8680. /**
  8681. * Adds a node filter function to the parser, the parser will collect the specified nodes by name
  8682. * and then execute the callback ones it has finished parsing the document.
  8683. *
  8684. * @example
  8685. * parser.addNodeFilter('p,h1', function(nodes, name) {
  8686. * for (var i = 0; i < nodes.length; i++) {
  8687. * console.log(nodes[i].name);
  8688. * }
  8689. * });
  8690. * @method addNodeFilter
  8691. * @method {String} name Comma separated list of nodes to collect.
  8692. * @param {function} callback Callback function to execute once it has collected nodes.
  8693. */
  8694. self.addNodeFilter = function(name, callback) {
  8695. each(explode(name), function(name) {
  8696. var list = nodeFilters[name];
  8697. if (!list) {
  8698. nodeFilters[name] = list = [];
  8699. }
  8700. list.push(callback);
  8701. });
  8702. };
  8703. /**
  8704. * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
  8705. * and then execute the callback ones it has finished parsing the document.
  8706. *
  8707. * @example
  8708. * parser.addAttributeFilter('src,href', function(nodes, name) {
  8709. * for (var i = 0; i < nodes.length; i++) {
  8710. * console.log(nodes[i].name);
  8711. * }
  8712. * });
  8713. * @method addAttributeFilter
  8714. * @method {String} name Comma separated list of nodes to collect.
  8715. * @param {function} callback Callback function to execute once it has collected nodes.
  8716. */
  8717. self.addAttributeFilter = function(name, callback) {
  8718. each(explode(name), function(name) {
  8719. var i;
  8720. for (i = 0; i < attributeFilters.length; i++) {
  8721. if (attributeFilters[i].name === name) {
  8722. attributeFilters[i].callbacks.push(callback);
  8723. return;
  8724. }
  8725. }
  8726. attributeFilters.push({name: name, callbacks: [callback]});
  8727. });
  8728. };
  8729. /**
  8730. * Parses the specified HTML string into a DOM like node tree and returns the result.
  8731. *
  8732. * @example
  8733. * var rootNode = new DomParser({...}).parse('<b>text</b>');
  8734. * @method parse
  8735. * @param {String} html Html string to sax parse.
  8736. * @param {Object} args Optional args object that gets passed to all filter functions.
  8737. * @return {tinymce.html.Node} Root node containing the tree.
  8738. */
  8739. self.parse = function(html, args) {
  8740. var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
  8741. var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
  8742. var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
  8743. var children, nonEmptyElements, rootBlockName;
  8744. args = args || {};
  8745. matchedNodes = {};
  8746. matchedAttributes = {};
  8747. blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
  8748. nonEmptyElements = schema.getNonEmptyElements();
  8749. children = schema.children;
  8750. validate = settings.validate;
  8751. rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
  8752. whiteSpaceElements = schema.getWhiteSpaceElements();
  8753. startWhiteSpaceRegExp = /^[ \t\r\n]+/;
  8754. endWhiteSpaceRegExp = /[ \t\r\n]+$/;
  8755. allWhiteSpaceRegExp = /[ \t\r\n]+/g;
  8756. isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
  8757. function addRootBlocks() {
  8758. var node = rootNode.firstChild, next, rootBlockNode;
  8759. // Removes whitespace at beginning and end of block so:
  8760. // <p> x </p> -> <p>x</p>
  8761. function trim(rootBlockNode) {
  8762. if (rootBlockNode) {
  8763. node = rootBlockNode.firstChild;
  8764. if (node && node.type == 3) {
  8765. node.value = node.value.replace(startWhiteSpaceRegExp, '');
  8766. }
  8767. node = rootBlockNode.lastChild;
  8768. if (node && node.type == 3) {
  8769. node.value = node.value.replace(endWhiteSpaceRegExp, '');
  8770. }
  8771. }
  8772. }
  8773. // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
  8774. if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
  8775. return;
  8776. }
  8777. while (node) {
  8778. next = node.next;
  8779. if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
  8780. !blockElements[node.name] && !node.attr('data-mce-type'))) {
  8781. if (!rootBlockNode) {
  8782. // Create a new root block element
  8783. rootBlockNode = createNode(rootBlockName, 1);
  8784. rootBlockNode.attr(settings.forced_root_block_attrs);
  8785. rootNode.insert(rootBlockNode, node);
  8786. rootBlockNode.append(node);
  8787. } else {
  8788. rootBlockNode.append(node);
  8789. }
  8790. } else {
  8791. trim(rootBlockNode);
  8792. rootBlockNode = null;
  8793. }
  8794. node = next;
  8795. }
  8796. trim(rootBlockNode);
  8797. }
  8798. function createNode(name, type) {
  8799. var node = new Node(name, type), list;
  8800. if (name in nodeFilters) {
  8801. list = matchedNodes[name];
  8802. if (list) {
  8803. list.push(node);
  8804. } else {
  8805. matchedNodes[name] = [node];
  8806. }
  8807. }
  8808. return node;
  8809. }
  8810. function removeWhitespaceBefore(node) {
  8811. var textNode, textVal, sibling;
  8812. for (textNode = node.prev; textNode && textNode.type === 3; ) {
  8813. textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
  8814. if (textVal.length > 0) {
  8815. textNode.value = textVal;
  8816. textNode = textNode.prev;
  8817. } else {
  8818. sibling = textNode.prev;
  8819. textNode.remove();
  8820. textNode = sibling;
  8821. }
  8822. }
  8823. }
  8824. function cloneAndExcludeBlocks(input) {
  8825. var name, output = {};
  8826. for (name in input) {
  8827. if (name !== 'li' && name != 'p') {
  8828. output[name] = input[name];
  8829. }
  8830. }
  8831. return output;
  8832. }
  8833. parser = new SaxParser({
  8834. validate: validate,
  8835. allow_script_urls: settings.allow_script_urls,
  8836. allow_conditional_comments: settings.allow_conditional_comments,
  8837. // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
  8838. self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
  8839. cdata: function(text) {
  8840. node.append(createNode('#cdata', 4)).value = text;
  8841. },
  8842. text: function(text, raw) {
  8843. var textNode;
  8844. // Trim all redundant whitespace on non white space elements
  8845. if (!isInWhiteSpacePreservedElement) {
  8846. text = text.replace(allWhiteSpaceRegExp, ' ');
  8847. if (node.lastChild && blockElements[node.lastChild.name]) {
  8848. text = text.replace(startWhiteSpaceRegExp, '');
  8849. }
  8850. }
  8851. // Do we need to create the node
  8852. if (text.length !== 0) {
  8853. textNode = createNode('#text', 3);
  8854. textNode.raw = !!raw;
  8855. node.append(textNode).value = text;
  8856. }
  8857. },
  8858. comment: function(text) {
  8859. node.append(createNode('#comment', 8)).value = text;
  8860. },
  8861. pi: function(name, text) {
  8862. node.append(createNode(name, 7)).value = text;
  8863. removeWhitespaceBefore(node);
  8864. },
  8865. doctype: function(text) {
  8866. var newNode;
  8867. newNode = node.append(createNode('#doctype', 10));
  8868. newNode.value = text;
  8869. removeWhitespaceBefore(node);
  8870. },
  8871. start: function(name, attrs, empty) {
  8872. var newNode, attrFiltersLen, elementRule, attrName, parent;
  8873. elementRule = validate ? schema.getElementRule(name) : {};
  8874. if (elementRule) {
  8875. newNode = createNode(elementRule.outputName || name, 1);
  8876. newNode.attributes = attrs;
  8877. newNode.shortEnded = empty;
  8878. node.append(newNode);
  8879. // Check if node is valid child of the parent node is the child is
  8880. // unknown we don't collect it since it's probably a custom element
  8881. parent = children[node.name];
  8882. if (parent && children[newNode.name] && !parent[newNode.name]) {
  8883. invalidChildren.push(newNode);
  8884. }
  8885. attrFiltersLen = attributeFilters.length;
  8886. while (attrFiltersLen--) {
  8887. attrName = attributeFilters[attrFiltersLen].name;
  8888. if (attrName in attrs.map) {
  8889. list = matchedAttributes[attrName];
  8890. if (list) {
  8891. list.push(newNode);
  8892. } else {
  8893. matchedAttributes[attrName] = [newNode];
  8894. }
  8895. }
  8896. }
  8897. // Trim whitespace before block
  8898. if (blockElements[name]) {
  8899. removeWhitespaceBefore(newNode);
  8900. }
  8901. // Change current node if the element wasn't empty i.e not <br /> or <img />
  8902. if (!empty) {
  8903. node = newNode;
  8904. }
  8905. // Check if we are inside a whitespace preserved element
  8906. if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  8907. isInWhiteSpacePreservedElement = true;
  8908. }
  8909. }
  8910. },
  8911. end: function(name) {
  8912. var textNode, elementRule, text, sibling, tempNode;
  8913. elementRule = validate ? schema.getElementRule(name) : {};
  8914. if (elementRule) {
  8915. if (blockElements[name]) {
  8916. if (!isInWhiteSpacePreservedElement) {
  8917. // Trim whitespace of the first node in a block
  8918. textNode = node.firstChild;
  8919. if (textNode && textNode.type === 3) {
  8920. text = textNode.value.replace(startWhiteSpaceRegExp, '');
  8921. // Any characters left after trim or should we remove it
  8922. if (text.length > 0) {
  8923. textNode.value = text;
  8924. textNode = textNode.next;
  8925. } else {
  8926. sibling = textNode.next;
  8927. textNode.remove();
  8928. textNode = sibling;
  8929. // Remove any pure whitespace siblings
  8930. while (textNode && textNode.type === 3) {
  8931. text = textNode.value;
  8932. sibling = textNode.next;
  8933. if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  8934. textNode.remove();
  8935. textNode = sibling;
  8936. }
  8937. textNode = sibling;
  8938. }
  8939. }
  8940. }
  8941. // Trim whitespace of the last node in a block
  8942. textNode = node.lastChild;
  8943. if (textNode && textNode.type === 3) {
  8944. text = textNode.value.replace(endWhiteSpaceRegExp, '');
  8945. // Any characters left after trim or should we remove it
  8946. if (text.length > 0) {
  8947. textNode.value = text;
  8948. textNode = textNode.prev;
  8949. } else {
  8950. sibling = textNode.prev;
  8951. textNode.remove();
  8952. textNode = sibling;
  8953. // Remove any pure whitespace siblings
  8954. while (textNode && textNode.type === 3) {
  8955. text = textNode.value;
  8956. sibling = textNode.prev;
  8957. if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  8958. textNode.remove();
  8959. textNode = sibling;
  8960. }
  8961. textNode = sibling;
  8962. }
  8963. }
  8964. }
  8965. }
  8966. // Trim start white space
  8967. // Removed due to: #5424
  8968. /*textNode = node.prev;
  8969. if (textNode && textNode.type === 3) {
  8970. text = textNode.value.replace(startWhiteSpaceRegExp, '');
  8971. if (text.length > 0)
  8972. textNode.value = text;
  8973. else
  8974. textNode.remove();
  8975. }*/
  8976. }
  8977. // Check if we exited a whitespace preserved element
  8978. if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  8979. isInWhiteSpacePreservedElement = false;
  8980. }
  8981. // Handle empty nodes
  8982. if (elementRule.removeEmpty || elementRule.paddEmpty) {
  8983. if (node.isEmpty(nonEmptyElements)) {
  8984. if (elementRule.paddEmpty) {
  8985. node.empty().append(new Node('#text', '3')).value = '\u00a0';
  8986. } else {
  8987. // Leave nodes that have a name like <a name="name">
  8988. if (!node.attributes.map.name && !node.attributes.map.id) {
  8989. tempNode = node.parent;
  8990. node.empty().remove();
  8991. node = tempNode;
  8992. return;
  8993. }
  8994. }
  8995. }
  8996. }
  8997. node = node.parent;
  8998. }
  8999. }
  9000. }, schema);
  9001. rootNode = node = new Node(args.context || settings.root_name, 11);
  9002. parser.parse(html);
  9003. // Fix invalid children or report invalid children in a contextual parsing
  9004. if (validate && invalidChildren.length) {
  9005. if (!args.context) {
  9006. fixInvalidChildren(invalidChildren);
  9007. } else {
  9008. args.invalid = true;
  9009. }
  9010. }
  9011. // Wrap nodes in the root into block elements if the root is body
  9012. if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
  9013. addRootBlocks();
  9014. }
  9015. // Run filters only when the contents is valid
  9016. if (!args.invalid) {
  9017. // Run node filters
  9018. for (name in matchedNodes) {
  9019. list = nodeFilters[name];
  9020. nodes = matchedNodes[name];
  9021. // Remove already removed children
  9022. fi = nodes.length;
  9023. while (fi--) {
  9024. if (!nodes[fi].parent) {
  9025. nodes.splice(fi, 1);
  9026. }
  9027. }
  9028. for (i = 0, l = list.length; i < l; i++) {
  9029. list[i](nodes, name, args);
  9030. }
  9031. }
  9032. // Run attribute filters
  9033. for (i = 0, l = attributeFilters.length; i < l; i++) {
  9034. list = attributeFilters[i];
  9035. if (list.name in matchedAttributes) {
  9036. nodes = matchedAttributes[list.name];
  9037. // Remove already removed children
  9038. fi = nodes.length;
  9039. while (fi--) {
  9040. if (!nodes[fi].parent) {
  9041. nodes.splice(fi, 1);
  9042. }
  9043. }
  9044. for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
  9045. list.callbacks[fi](nodes, list.name, args);
  9046. }
  9047. }
  9048. }
  9049. }
  9050. return rootNode;
  9051. };
  9052. // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
  9053. // make it possible to place the caret inside empty blocks. This logic tries to remove
  9054. // these elements and keep br elements that where intended to be there intact
  9055. if (settings.remove_trailing_brs) {
  9056. self.addNodeFilter('br', function(nodes) {
  9057. var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
  9058. var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
  9059. var elementRule, textNode;
  9060. // Remove brs from body element as well
  9061. blockElements.body = 1;
  9062. // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
  9063. for (i = 0; i < l; i++) {
  9064. node = nodes[i];
  9065. parent = node.parent;
  9066. if (blockElements[node.parent.name] && node === parent.lastChild) {
  9067. // Loop all nodes to the left of the current node and check for other BR elements
  9068. // excluding bookmarks since they are invisible
  9069. prev = node.prev;
  9070. while (prev) {
  9071. prevName = prev.name;
  9072. // Ignore bookmarks
  9073. if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
  9074. // Found a non BR element
  9075. if (prevName !== "br") {
  9076. break;
  9077. }
  9078. // Found another br it's a <br><br> structure then don't remove anything
  9079. if (prevName === 'br') {
  9080. node = null;
  9081. break;
  9082. }
  9083. }
  9084. prev = prev.prev;
  9085. }
  9086. if (node) {
  9087. node.remove();
  9088. // Is the parent to be considered empty after we removed the BR
  9089. if (parent.isEmpty(nonEmptyElements)) {
  9090. elementRule = schema.getElementRule(parent.name);
  9091. // Remove or padd the element depending on schema rule
  9092. if (elementRule) {
  9093. if (elementRule.removeEmpty) {
  9094. parent.remove();
  9095. } else if (elementRule.paddEmpty) {
  9096. parent.empty().append(new Node('#text', 3)).value = '\u00a0';
  9097. }
  9098. }
  9099. }
  9100. }
  9101. } else {
  9102. // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
  9103. // so they become <p><b><i>&nbsp;</i></b></p>
  9104. lastParent = node;
  9105. while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
  9106. lastParent = parent;
  9107. if (blockElements[parent.name]) {
  9108. break;
  9109. }
  9110. parent = parent.parent;
  9111. }
  9112. if (lastParent === parent) {
  9113. textNode = new Node('#text', 3);
  9114. textNode.value = '\u00a0';
  9115. node.replace(textNode);
  9116. }
  9117. }
  9118. }
  9119. });
  9120. }
  9121. // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
  9122. if (!settings.allow_html_in_named_anchor) {
  9123. self.addAttributeFilter('id,name', function(nodes) {
  9124. var i = nodes.length, sibling, prevSibling, parent, node;
  9125. while (i--) {
  9126. node = nodes[i];
  9127. if (node.name === 'a' && node.firstChild && !node.attr('href')) {
  9128. parent = node.parent;
  9129. // Move children after current node
  9130. sibling = node.lastChild;
  9131. do {
  9132. prevSibling = sibling.prev;
  9133. parent.insert(sibling, node);
  9134. sibling = prevSibling;
  9135. } while (sibling);
  9136. }
  9137. }
  9138. });
  9139. }
  9140. };
  9141. });
  9142. // Included from: js/tinymce/classes/html/Writer.js
  9143. /**
  9144. * Writer.js
  9145. *
  9146. * Copyright, Moxiecode Systems AB
  9147. * Released under LGPL License.
  9148. *
  9149. * License: http://www.tinymce.com/license
  9150. * Contributing: http://www.tinymce.com/contributing
  9151. */
  9152. /**
  9153. * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
  9154. *
  9155. * @class tinymce.html.Writer
  9156. * @example
  9157. * var writer = new tinymce.html.Writer({indent: true});
  9158. * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
  9159. * console.log(writer.getContent());
  9160. *
  9161. * @class tinymce.html.Writer
  9162. * @version 3.4
  9163. */
  9164. define("tinymce/html/Writer", [
  9165. "tinymce/html/Entities",
  9166. "tinymce/util/Tools"
  9167. ], function(Entities, Tools) {
  9168. var makeMap = Tools.makeMap;
  9169. /**
  9170. * Constructs a new Writer instance.
  9171. *
  9172. * @constructor
  9173. * @method Writer
  9174. * @param {Object} settings Name/value settings object.
  9175. */
  9176. return function(settings) {
  9177. var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
  9178. settings = settings || {};
  9179. indent = settings.indent;
  9180. indentBefore = makeMap(settings.indent_before || '');
  9181. indentAfter = makeMap(settings.indent_after || '');
  9182. encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
  9183. htmlOutput = settings.element_format == "html";
  9184. return {
  9185. /**
  9186. * Writes the a start element such as <p id="a">.
  9187. *
  9188. * @method start
  9189. * @param {String} name Name of the element.
  9190. * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
  9191. * @param {Boolean} empty Optional empty state if the tag should end like <br />.
  9192. */
  9193. start: function(name, attrs, empty) {
  9194. var i, l, attr, value;
  9195. if (indent && indentBefore[name] && html.length > 0) {
  9196. value = html[html.length - 1];
  9197. if (value.length > 0 && value !== '\n') {
  9198. html.push('\n');
  9199. }
  9200. }
  9201. html.push('<', name);
  9202. if (attrs) {
  9203. for (i = 0, l = attrs.length; i < l; i++) {
  9204. attr = attrs[i];
  9205. html.push(' ', attr.name, '="', encode(attr.value, true), '"');
  9206. }
  9207. }
  9208. if (!empty || htmlOutput) {
  9209. html[html.length] = '>';
  9210. } else {
  9211. html[html.length] = ' />';
  9212. }
  9213. if (empty && indent && indentAfter[name] && html.length > 0) {
  9214. value = html[html.length - 1];
  9215. if (value.length > 0 && value !== '\n') {
  9216. html.push('\n');
  9217. }
  9218. }
  9219. },
  9220. /**
  9221. * Writes the a end element such as </p>.
  9222. *
  9223. * @method end
  9224. * @param {String} name Name of the element.
  9225. */
  9226. end: function(name) {
  9227. var value;
  9228. /*if (indent && indentBefore[name] && html.length > 0) {
  9229. value = html[html.length - 1];
  9230. if (value.length > 0 && value !== '\n')
  9231. html.push('\n');
  9232. }*/
  9233. html.push('</', name, '>');
  9234. if (indent && indentAfter[name] && html.length > 0) {
  9235. value = html[html.length - 1];
  9236. if (value.length > 0 && value !== '\n') {
  9237. html.push('\n');
  9238. }
  9239. }
  9240. },
  9241. /**
  9242. * Writes a text node.
  9243. *
  9244. * @method text
  9245. * @param {String} text String to write out.
  9246. * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
  9247. */
  9248. text: function(text, raw) {
  9249. if (text.length > 0) {
  9250. html[html.length] = raw ? text : encode(text);
  9251. }
  9252. },
  9253. /**
  9254. * Writes a cdata node such as <![CDATA[data]]>.
  9255. *
  9256. * @method cdata
  9257. * @param {String} text String to write out inside the cdata.
  9258. */
  9259. cdata: function(text) {
  9260. html.push('<![CDATA[', text, ']]>');
  9261. },
  9262. /**
  9263. * Writes a comment node such as <!-- Comment -->.
  9264. *
  9265. * @method cdata
  9266. * @param {String} text String to write out inside the comment.
  9267. */
  9268. comment: function(text) {
  9269. html.push('<!--', text, '-->');
  9270. },
  9271. /**
  9272. * Writes a PI node such as <?xml attr="value" ?>.
  9273. *
  9274. * @method pi
  9275. * @param {String} name Name of the pi.
  9276. * @param {String} text String to write out inside the pi.
  9277. */
  9278. pi: function(name, text) {
  9279. if (text) {
  9280. html.push('<?', name, ' ', text, '?>');
  9281. } else {
  9282. html.push('<?', name, '?>');
  9283. }
  9284. if (indent) {
  9285. html.push('\n');
  9286. }
  9287. },
  9288. /**
  9289. * Writes a doctype node such as <!DOCTYPE data>.
  9290. *
  9291. * @method doctype
  9292. * @param {String} text String to write out inside the doctype.
  9293. */
  9294. doctype: function(text) {
  9295. html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
  9296. },
  9297. /**
  9298. * Resets the internal buffer if one wants to reuse the writer.
  9299. *
  9300. * @method reset
  9301. */
  9302. reset: function() {
  9303. html.length = 0;
  9304. },
  9305. /**
  9306. * Returns the contents that got serialized.
  9307. *
  9308. * @method getContent
  9309. * @return {String} HTML contents that got written down.
  9310. */
  9311. getContent: function() {
  9312. return html.join('').replace(/\n$/, '');
  9313. }
  9314. };
  9315. };
  9316. });
  9317. // Included from: js/tinymce/classes/html/Serializer.js
  9318. /**
  9319. * Serializer.js
  9320. *
  9321. * Copyright, Moxiecode Systems AB
  9322. * Released under LGPL License.
  9323. *
  9324. * License: http://www.tinymce.com/license
  9325. * Contributing: http://www.tinymce.com/contributing
  9326. */
  9327. /**
  9328. * This class is used to serialize down the DOM tree into a string using a Writer instance.
  9329. *
  9330. *
  9331. * @example
  9332. * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
  9333. * @class tinymce.html.Serializer
  9334. * @version 3.4
  9335. */
  9336. define("tinymce/html/Serializer", [
  9337. "tinymce/html/Writer",
  9338. "tinymce/html/Schema"
  9339. ], function(Writer, Schema) {
  9340. /**
  9341. * Constructs a new Serializer instance.
  9342. *
  9343. * @constructor
  9344. * @method Serializer
  9345. * @param {Object} settings Name/value settings object.
  9346. * @param {tinymce.html.Schema} schema Schema instance to use.
  9347. */
  9348. return function(settings, schema) {
  9349. var self = this, writer = new Writer(settings);
  9350. settings = settings || {};
  9351. settings.validate = "validate" in settings ? settings.validate : true;
  9352. self.schema = schema = schema || new Schema();
  9353. self.writer = writer;
  9354. /**
  9355. * Serializes the specified node into a string.
  9356. *
  9357. * @example
  9358. * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
  9359. * @method serialize
  9360. * @param {tinymce.html.Node} node Node instance to serialize.
  9361. * @return {String} String with HTML based on DOM tree.
  9362. */
  9363. self.serialize = function(node) {
  9364. var handlers, validate;
  9365. validate = settings.validate;
  9366. handlers = {
  9367. // #text
  9368. 3: function(node) {
  9369. writer.text(node.value, node.raw);
  9370. },
  9371. // #comment
  9372. 8: function(node) {
  9373. writer.comment(node.value);
  9374. },
  9375. // Processing instruction
  9376. 7: function(node) {
  9377. writer.pi(node.name, node.value);
  9378. },
  9379. // Doctype
  9380. 10: function(node) {
  9381. writer.doctype(node.value);
  9382. },
  9383. // CDATA
  9384. 4: function(node) {
  9385. writer.cdata(node.value);
  9386. },
  9387. // Document fragment
  9388. 11: function(node) {
  9389. if ((node = node.firstChild)) {
  9390. do {
  9391. walk(node);
  9392. } while ((node = node.next));
  9393. }
  9394. }
  9395. };
  9396. writer.reset();
  9397. function walk(node) {
  9398. var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
  9399. if (!handler) {
  9400. name = node.name;
  9401. isEmpty = node.shortEnded;
  9402. attrs = node.attributes;
  9403. // Sort attributes
  9404. if (validate && attrs && attrs.length > 1) {
  9405. sortedAttrs = [];
  9406. sortedAttrs.map = {};
  9407. elementRule = schema.getElementRule(node.name);
  9408. for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
  9409. attrName = elementRule.attributesOrder[i];
  9410. if (attrName in attrs.map) {
  9411. attrValue = attrs.map[attrName];
  9412. sortedAttrs.map[attrName] = attrValue;
  9413. sortedAttrs.push({name: attrName, value: attrValue});
  9414. }
  9415. }
  9416. for (i = 0, l = attrs.length; i < l; i++) {
  9417. attrName = attrs[i].name;
  9418. if (!(attrName in sortedAttrs.map)) {
  9419. attrValue = attrs.map[attrName];
  9420. sortedAttrs.map[attrName] = attrValue;
  9421. sortedAttrs.push({name: attrName, value: attrValue});
  9422. }
  9423. }
  9424. attrs = sortedAttrs;
  9425. }
  9426. writer.start(node.name, attrs, isEmpty);
  9427. if (!isEmpty) {
  9428. if ((node = node.firstChild)) {
  9429. do {
  9430. walk(node);
  9431. } while ((node = node.next));
  9432. }
  9433. writer.end(name);
  9434. }
  9435. } else {
  9436. handler(node);
  9437. }
  9438. }
  9439. // Serialize element and treat all non elements as fragments
  9440. if (node.type == 1 && !settings.inner) {
  9441. walk(node);
  9442. } else {
  9443. handlers[11](node);
  9444. }
  9445. return writer.getContent();
  9446. };
  9447. };
  9448. });
  9449. // Included from: js/tinymce/classes/dom/Serializer.js
  9450. /**
  9451. * Serializer.js
  9452. *
  9453. * Copyright, Moxiecode Systems AB
  9454. * Released under LGPL License.
  9455. *
  9456. * License: http://www.tinymce.com/license
  9457. * Contributing: http://www.tinymce.com/contributing
  9458. */
  9459. /**
  9460. * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
  9461. * more details and examples on how to use this class.
  9462. *
  9463. * @class tinymce.dom.Serializer
  9464. */
  9465. define("tinymce/dom/Serializer", [
  9466. "tinymce/dom/DOMUtils",
  9467. "tinymce/html/DomParser",
  9468. "tinymce/html/Entities",
  9469. "tinymce/html/Serializer",
  9470. "tinymce/html/Node",
  9471. "tinymce/html/Schema",
  9472. "tinymce/Env",
  9473. "tinymce/util/Tools"
  9474. ], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
  9475. var each = Tools.each, trim = Tools.trim;
  9476. var DOM = DOMUtils.DOM;
  9477. /**
  9478. * Constructs a new DOM serializer class.
  9479. *
  9480. * @constructor
  9481. * @method Serializer
  9482. * @param {Object} settings Serializer settings object.
  9483. * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
  9484. */
  9485. return function(settings, editor) {
  9486. var dom, schema, htmlParser;
  9487. if (editor) {
  9488. dom = editor.dom;
  9489. schema = editor.schema;
  9490. }
  9491. // Default DOM and Schema if they are undefined
  9492. dom = dom || DOM;
  9493. schema = schema || new Schema(settings);
  9494. settings.entity_encoding = settings.entity_encoding || 'named';
  9495. settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
  9496. htmlParser = new DomParser(settings, schema);
  9497. // Convert tabindex back to elements when serializing contents
  9498. htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
  9499. var i = nodes.length, node;
  9500. while (i--) {
  9501. node = nodes[i];
  9502. node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
  9503. node.attr(name, null);
  9504. }
  9505. });
  9506. // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
  9507. htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
  9508. var i = nodes.length, node, value, internalName = 'data-mce-' + name;
  9509. var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
  9510. while (i--) {
  9511. node = nodes[i];
  9512. value = node.attributes.map[internalName];
  9513. if (value !== undef) {
  9514. // Set external name to internal value and remove internal
  9515. node.attr(name, value.length > 0 ? value : null);
  9516. node.attr(internalName, null);
  9517. } else {
  9518. // No internal attribute found then convert the value we have in the DOM
  9519. value = node.attributes.map[name];
  9520. if (name === "style") {
  9521. value = dom.serializeStyle(dom.parseStyle(value), node.name);
  9522. } else if (urlConverter) {
  9523. value = urlConverter.call(urlConverterScope, value, name, node.name);
  9524. }
  9525. node.attr(name, value.length > 0 ? value : null);
  9526. }
  9527. }
  9528. });
  9529. // Remove internal classes mceItem<..> or mceSelected
  9530. htmlParser.addAttributeFilter('class', function(nodes) {
  9531. var i = nodes.length, node, value;
  9532. while (i--) {
  9533. node = nodes[i];
  9534. value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
  9535. node.attr('class', value.length > 0 ? value : null);
  9536. }
  9537. });
  9538. // Remove bookmark elements
  9539. htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
  9540. var i = nodes.length, node;
  9541. while (i--) {
  9542. node = nodes[i];
  9543. if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
  9544. node.remove();
  9545. }
  9546. }
  9547. });
  9548. // Remove expando attributes
  9549. htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) {
  9550. var i = nodes.length;
  9551. while (i--) {
  9552. nodes[i].attr(name, null);
  9553. }
  9554. });
  9555. htmlParser.addNodeFilter('noscript', function(nodes) {
  9556. var i = nodes.length, node;
  9557. while (i--) {
  9558. node = nodes[i].firstChild;
  9559. if (node) {
  9560. node.value = Entities.decode(node.value);
  9561. }
  9562. }
  9563. });
  9564. // Force script into CDATA sections and remove the mce- prefix also add comments around styles
  9565. htmlParser.addNodeFilter('script,style', function(nodes, name) {
  9566. var i = nodes.length, node, value;
  9567. function trim(value) {
  9568. /*jshint maxlen:255 */
  9569. /*eslint max-len:0 */
  9570. return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
  9571. .replace(/^[\r\n]*|[\r\n]*$/g, '')
  9572. .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
  9573. .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
  9574. }
  9575. while (i--) {
  9576. node = nodes[i];
  9577. value = node.firstChild ? node.firstChild.value : '';
  9578. if (name === "script") {
  9579. // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5)
  9580. var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, '');
  9581. node.attr('type', type === 'text/javascript' ? null : type);
  9582. if (value.length > 0) {
  9583. node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
  9584. }
  9585. } else {
  9586. if (value.length > 0) {
  9587. node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
  9588. }
  9589. }
  9590. }
  9591. });
  9592. // Convert comments to cdata and handle protected comments
  9593. htmlParser.addNodeFilter('#comment', function(nodes) {
  9594. var i = nodes.length, node;
  9595. while (i--) {
  9596. node = nodes[i];
  9597. if (node.value.indexOf('[CDATA[') === 0) {
  9598. node.name = '#cdata';
  9599. node.type = 4;
  9600. node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
  9601. } else if (node.value.indexOf('mce:protected ') === 0) {
  9602. node.name = "#text";
  9603. node.type = 3;
  9604. node.raw = true;
  9605. node.value = unescape(node.value).substr(14);
  9606. }
  9607. }
  9608. });
  9609. htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
  9610. var i = nodes.length, node;
  9611. while (i--) {
  9612. node = nodes[i];
  9613. if (node.type === 7) {
  9614. node.remove();
  9615. } else if (node.type === 1) {
  9616. if (name === "input" && !("type" in node.attributes.map)) {
  9617. node.attr('type', 'text');
  9618. }
  9619. }
  9620. }
  9621. });
  9622. // Fix list elements, TODO: Replace this later
  9623. if (settings.fix_list_elements) {
  9624. htmlParser.addNodeFilter('ul,ol', function(nodes) {
  9625. var i = nodes.length, node, parentNode;
  9626. while (i--) {
  9627. node = nodes[i];
  9628. parentNode = node.parent;
  9629. if (parentNode.name === 'ul' || parentNode.name === 'ol') {
  9630. if (node.prev && node.prev.name === 'li') {
  9631. node.prev.append(node);
  9632. }
  9633. }
  9634. }
  9635. });
  9636. }
  9637. // Remove internal data attributes
  9638. htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) {
  9639. var i = nodes.length;
  9640. while (i--) {
  9641. nodes[i].attr(name, null);
  9642. }
  9643. });
  9644. // Return public methods
  9645. return {
  9646. /**
  9647. * Schema instance that was used to when the Serializer was constructed.
  9648. *
  9649. * @field {tinymce.html.Schema} schema
  9650. */
  9651. schema: schema,
  9652. /**
  9653. * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
  9654. * and then execute the callback ones it has finished parsing the document.
  9655. *
  9656. * @example
  9657. * parser.addNodeFilter('p,h1', function(nodes, name) {
  9658. * for (var i = 0; i < nodes.length; i++) {
  9659. * console.log(nodes[i].name);
  9660. * }
  9661. * });
  9662. * @method addNodeFilter
  9663. * @method {String} name Comma separated list of nodes to collect.
  9664. * @param {function} callback Callback function to execute once it has collected nodes.
  9665. */
  9666. addNodeFilter: htmlParser.addNodeFilter,
  9667. /**
  9668. * Adds a attribute filter function to the parser used by the serializer, the parser will
  9669. * collect nodes that has the specified attributes
  9670. * and then execute the callback ones it has finished parsing the document.
  9671. *
  9672. * @example
  9673. * parser.addAttributeFilter('src,href', function(nodes, name) {
  9674. * for (var i = 0; i < nodes.length; i++) {
  9675. * console.log(nodes[i].name);
  9676. * }
  9677. * });
  9678. * @method addAttributeFilter
  9679. * @method {String} name Comma separated list of nodes to collect.
  9680. * @param {function} callback Callback function to execute once it has collected nodes.
  9681. */
  9682. addAttributeFilter: htmlParser.addAttributeFilter,
  9683. /**
  9684. * Serializes the specified browser DOM node into a HTML string.
  9685. *
  9686. * @method serialize
  9687. * @param {DOMNode} node DOM node to serialize.
  9688. * @param {Object} args Arguments option that gets passed to event handlers.
  9689. */
  9690. serialize: function(node, args) {
  9691. var self = this, impl, doc, oldDoc, htmlSerializer, content;
  9692. // Explorer won't clone contents of script and style and the
  9693. // selected index of select elements are cleared on a clone operation.
  9694. if (Env.ie && dom.select('script,style,select,map').length > 0) {
  9695. content = node.innerHTML;
  9696. node = node.cloneNode(false);
  9697. dom.setHTML(node, content);
  9698. } else {
  9699. node = node.cloneNode(true);
  9700. }
  9701. // Nodes needs to be attached to something in WebKit/Opera
  9702. // This fix will make DOM ranges and make Sizzle happy!
  9703. impl = node.ownerDocument.implementation;
  9704. if (impl.createHTMLDocument) {
  9705. // Create an empty HTML document
  9706. doc = impl.createHTMLDocument("");
  9707. // Add the element or it's children if it's a body element to the new document
  9708. each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
  9709. doc.body.appendChild(doc.importNode(node, true));
  9710. });
  9711. // Grab first child or body element for serialization
  9712. if (node.nodeName != 'BODY') {
  9713. node = doc.body.firstChild;
  9714. } else {
  9715. node = doc.body;
  9716. }
  9717. // set the new document in DOMUtils so createElement etc works
  9718. oldDoc = dom.doc;
  9719. dom.doc = doc;
  9720. }
  9721. args = args || {};
  9722. args.format = args.format || 'html';
  9723. // Don't wrap content if we want selected html
  9724. if (args.selection) {
  9725. args.forced_root_block = '';
  9726. }
  9727. // Pre process
  9728. if (!args.no_events) {
  9729. args.node = node;
  9730. self.onPreProcess(args);
  9731. }
  9732. // Setup serializer
  9733. htmlSerializer = new Serializer(settings, schema);
  9734. // Parse and serialize HTML
  9735. args.content = htmlSerializer.serialize(
  9736. htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
  9737. );
  9738. // Replace all BOM characters for now until we can find a better solution
  9739. if (!args.cleanup) {
  9740. args.content = args.content.replace(/\uFEFF/g, '');
  9741. }
  9742. // Post process
  9743. if (!args.no_events) {
  9744. self.onPostProcess(args);
  9745. }
  9746. // Restore the old document if it was changed
  9747. if (oldDoc) {
  9748. dom.doc = oldDoc;
  9749. }
  9750. args.node = null;
  9751. return args.content;
  9752. },
  9753. /**
  9754. * Adds valid elements rules to the serializers schema instance this enables you to specify things
  9755. * like what elements should be outputted and what attributes specific elements might have.
  9756. * Consult the Wiki for more details on this format.
  9757. *
  9758. * @method addRules
  9759. * @param {String} rules Valid elements rules string to add to schema.
  9760. */
  9761. addRules: function(rules) {
  9762. schema.addValidElements(rules);
  9763. },
  9764. /**
  9765. * Sets the valid elements rules to the serializers schema instance this enables you to specify things
  9766. * like what elements should be outputted and what attributes specific elements might have.
  9767. * Consult the Wiki for more details on this format.
  9768. *
  9769. * @method setRules
  9770. * @param {String} rules Valid elements rules string.
  9771. */
  9772. setRules: function(rules) {
  9773. schema.setValidElements(rules);
  9774. },
  9775. onPreProcess: function(args) {
  9776. if (editor) {
  9777. editor.fire('PreProcess', args);
  9778. }
  9779. },
  9780. onPostProcess: function(args) {
  9781. if (editor) {
  9782. editor.fire('PostProcess', args);
  9783. }
  9784. }
  9785. };
  9786. };
  9787. });
  9788. // Included from: js/tinymce/classes/dom/TridentSelection.js
  9789. /**
  9790. * TridentSelection.js
  9791. *
  9792. * Copyright, Moxiecode Systems AB
  9793. * Released under LGPL License.
  9794. *
  9795. * License: http://www.tinymce.com/license
  9796. * Contributing: http://www.tinymce.com/contributing
  9797. */
  9798. /**
  9799. * Selection class for old explorer versions. This one fakes the
  9800. * native selection object available on modern browsers.
  9801. *
  9802. * @class tinymce.dom.TridentSelection
  9803. */
  9804. define("tinymce/dom/TridentSelection", [], function() {
  9805. function Selection(selection) {
  9806. var self = this, dom = selection.dom, FALSE = false;
  9807. function getPosition(rng, start) {
  9808. var checkRng, startIndex = 0, endIndex, inside,
  9809. children, child, offset, index, position = -1, parent;
  9810. // Setup test range, collapse it and get the parent
  9811. checkRng = rng.duplicate();
  9812. checkRng.collapse(start);
  9813. parent = checkRng.parentElement();
  9814. // Check if the selection is within the right document
  9815. if (parent.ownerDocument !== selection.dom.doc) {
  9816. return;
  9817. }
  9818. // IE will report non editable elements as it's parent so look for an editable one
  9819. while (parent.contentEditable === "false") {
  9820. parent = parent.parentNode;
  9821. }
  9822. // If parent doesn't have any children then return that we are inside the element
  9823. if (!parent.hasChildNodes()) {
  9824. return {node: parent, inside: 1};
  9825. }
  9826. // Setup node list and endIndex
  9827. children = parent.children;
  9828. endIndex = children.length - 1;
  9829. // Perform a binary search for the position
  9830. while (startIndex <= endIndex) {
  9831. index = Math.floor((startIndex + endIndex) / 2);
  9832. // Move selection to node and compare the ranges
  9833. child = children[index];
  9834. checkRng.moveToElementText(child);
  9835. position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
  9836. // Before/after or an exact match
  9837. if (position > 0) {
  9838. endIndex = index - 1;
  9839. } else if (position < 0) {
  9840. startIndex = index + 1;
  9841. } else {
  9842. return {node: child};
  9843. }
  9844. }
  9845. // Check if child position is before or we didn't find a position
  9846. if (position < 0) {
  9847. // No element child was found use the parent element and the offset inside that
  9848. if (!child) {
  9849. checkRng.moveToElementText(parent);
  9850. checkRng.collapse(true);
  9851. child = parent;
  9852. inside = true;
  9853. } else {
  9854. checkRng.collapse(false);
  9855. }
  9856. // Walk character by character in text node until we hit the selected range endpoint,
  9857. // hit the end of document or parent isn't the right one
  9858. // We need to walk char by char since rng.text or rng.htmlText will trim line endings
  9859. offset = 0;
  9860. while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  9861. if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
  9862. break;
  9863. }
  9864. offset++;
  9865. }
  9866. } else {
  9867. // Child position is after the selection endpoint
  9868. checkRng.collapse(true);
  9869. // Walk character by character in text node until we hit the selected range endpoint, hit
  9870. // the end of document or parent isn't the right one
  9871. offset = 0;
  9872. while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  9873. if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
  9874. break;
  9875. }
  9876. offset++;
  9877. }
  9878. }
  9879. return {node: child, position: position, offset: offset, inside: inside};
  9880. }
  9881. // Returns a W3C DOM compatible range object by using the IE Range API
  9882. function getRange() {
  9883. var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
  9884. // If selection is outside the current document just return an empty range
  9885. element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
  9886. if (element.ownerDocument != dom.doc) {
  9887. return domRange;
  9888. }
  9889. collapsed = selection.isCollapsed();
  9890. // Handle control selection
  9891. if (ieRange.item) {
  9892. domRange.setStart(element.parentNode, dom.nodeIndex(element));
  9893. domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
  9894. return domRange;
  9895. }
  9896. function findEndPoint(start) {
  9897. var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
  9898. container = endPoint.node;
  9899. offset = endPoint.offset;
  9900. if (endPoint.inside && !container.hasChildNodes()) {
  9901. domRange[start ? 'setStart' : 'setEnd'](container, 0);
  9902. return;
  9903. }
  9904. if (offset === undef) {
  9905. domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
  9906. return;
  9907. }
  9908. if (endPoint.position < 0) {
  9909. sibling = endPoint.inside ? container.firstChild : container.nextSibling;
  9910. if (!sibling) {
  9911. domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
  9912. return;
  9913. }
  9914. if (!offset) {
  9915. if (sibling.nodeType == 3) {
  9916. domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
  9917. } else {
  9918. domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
  9919. }
  9920. return;
  9921. }
  9922. // Find the text node and offset
  9923. while (sibling) {
  9924. nodeValue = sibling.nodeValue;
  9925. textNodeOffset += nodeValue.length;
  9926. // We are at or passed the position we where looking for
  9927. if (textNodeOffset >= offset) {
  9928. container = sibling;
  9929. textNodeOffset -= offset;
  9930. textNodeOffset = nodeValue.length - textNodeOffset;
  9931. break;
  9932. }
  9933. sibling = sibling.nextSibling;
  9934. }
  9935. } else {
  9936. // Find the text node and offset
  9937. sibling = container.previousSibling;
  9938. if (!sibling) {
  9939. return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
  9940. }
  9941. // If there isn't any text to loop then use the first position
  9942. if (!offset) {
  9943. if (container.nodeType == 3) {
  9944. domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
  9945. } else {
  9946. domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
  9947. }
  9948. return;
  9949. }
  9950. while (sibling) {
  9951. textNodeOffset += sibling.nodeValue.length;
  9952. // We are at or passed the position we where looking for
  9953. if (textNodeOffset >= offset) {
  9954. container = sibling;
  9955. textNodeOffset -= offset;
  9956. break;
  9957. }
  9958. sibling = sibling.previousSibling;
  9959. }
  9960. }
  9961. domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
  9962. }
  9963. try {
  9964. // Find start point
  9965. findEndPoint(true);
  9966. // Find end point if needed
  9967. if (!collapsed) {
  9968. findEndPoint();
  9969. }
  9970. } catch (ex) {
  9971. // IE has a nasty bug where text nodes might throw "invalid argument" when you
  9972. // access the nodeValue or other properties of text nodes. This seems to happend when
  9973. // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
  9974. if (ex.number == -2147024809) {
  9975. // Get the current selection
  9976. bookmark = self.getBookmark(2);
  9977. // Get start element
  9978. tmpRange = ieRange.duplicate();
  9979. tmpRange.collapse(true);
  9980. element = tmpRange.parentElement();
  9981. // Get end element
  9982. if (!collapsed) {
  9983. tmpRange = ieRange.duplicate();
  9984. tmpRange.collapse(false);
  9985. element2 = tmpRange.parentElement();
  9986. element2.innerHTML = element2.innerHTML;
  9987. }
  9988. // Remove the broken elements
  9989. element.innerHTML = element.innerHTML;
  9990. // Restore the selection
  9991. self.moveToBookmark(bookmark);
  9992. // Since the range has moved we need to re-get it
  9993. ieRange = selection.getRng();
  9994. // Find start point
  9995. findEndPoint(true);
  9996. // Find end point if needed
  9997. if (!collapsed) {
  9998. findEndPoint();
  9999. }
  10000. } else {
  10001. throw ex; // Throw other errors
  10002. }
  10003. }
  10004. return domRange;
  10005. }
  10006. this.getBookmark = function(type) {
  10007. var rng = selection.getRng(), bookmark = {};
  10008. function getIndexes(node) {
  10009. var parent, root, children, i, indexes = [];
  10010. parent = node.parentNode;
  10011. root = dom.getRoot().parentNode;
  10012. while (parent != root && parent.nodeType !== 9) {
  10013. children = parent.children;
  10014. i = children.length;
  10015. while (i--) {
  10016. if (node === children[i]) {
  10017. indexes.push(i);
  10018. break;
  10019. }
  10020. }
  10021. node = parent;
  10022. parent = parent.parentNode;
  10023. }
  10024. return indexes;
  10025. }
  10026. function getBookmarkEndPoint(start) {
  10027. var position;
  10028. position = getPosition(rng, start);
  10029. if (position) {
  10030. return {
  10031. position: position.position,
  10032. offset: position.offset,
  10033. indexes: getIndexes(position.node),
  10034. inside: position.inside
  10035. };
  10036. }
  10037. }
  10038. // Non ubstructive bookmark
  10039. if (type === 2) {
  10040. // Handle text selection
  10041. if (!rng.item) {
  10042. bookmark.start = getBookmarkEndPoint(true);
  10043. if (!selection.isCollapsed()) {
  10044. bookmark.end = getBookmarkEndPoint();
  10045. }
  10046. } else {
  10047. bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
  10048. }
  10049. }
  10050. return bookmark;
  10051. };
  10052. this.moveToBookmark = function(bookmark) {
  10053. var rng, body = dom.doc.body;
  10054. function resolveIndexes(indexes) {
  10055. var node, i, idx, children;
  10056. node = dom.getRoot();
  10057. for (i = indexes.length - 1; i >= 0; i--) {
  10058. children = node.children;
  10059. idx = indexes[i];
  10060. if (idx <= children.length - 1) {
  10061. node = children[idx];
  10062. }
  10063. }
  10064. return node;
  10065. }
  10066. function setBookmarkEndPoint(start) {
  10067. var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
  10068. if (endPoint) {
  10069. moveLeft = endPoint.position > 0;
  10070. moveRng = body.createTextRange();
  10071. moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
  10072. offset = endPoint.offset;
  10073. if (offset !== undef) {
  10074. moveRng.collapse(endPoint.inside || moveLeft);
  10075. moveRng.moveStart('character', moveLeft ? -offset : offset);
  10076. } else {
  10077. moveRng.collapse(start);
  10078. }
  10079. rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
  10080. if (start) {
  10081. rng.collapse(true);
  10082. }
  10083. }
  10084. }
  10085. if (bookmark.start) {
  10086. if (bookmark.start.ctrl) {
  10087. rng = body.createControlRange();
  10088. rng.addElement(resolveIndexes(bookmark.start.indexes));
  10089. rng.select();
  10090. } else {
  10091. rng = body.createTextRange();
  10092. setBookmarkEndPoint(true);
  10093. setBookmarkEndPoint();
  10094. rng.select();
  10095. }
  10096. }
  10097. };
  10098. this.addRange = function(rng) {
  10099. var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
  10100. doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
  10101. function setEndPoint(start) {
  10102. var container, offset, marker, tmpRng, nodes;
  10103. marker = dom.create('a');
  10104. container = start ? startContainer : endContainer;
  10105. offset = start ? startOffset : endOffset;
  10106. tmpRng = ieRng.duplicate();
  10107. if (container == doc || container == doc.documentElement) {
  10108. container = body;
  10109. offset = 0;
  10110. }
  10111. if (container.nodeType == 3) {
  10112. container.parentNode.insertBefore(marker, container);
  10113. tmpRng.moveToElementText(marker);
  10114. tmpRng.moveStart('character', offset);
  10115. dom.remove(marker);
  10116. ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  10117. } else {
  10118. nodes = container.childNodes;
  10119. if (nodes.length) {
  10120. if (offset >= nodes.length) {
  10121. dom.insertAfter(marker, nodes[nodes.length - 1]);
  10122. } else {
  10123. container.insertBefore(marker, nodes[offset]);
  10124. }
  10125. tmpRng.moveToElementText(marker);
  10126. } else if (container.canHaveHTML) {
  10127. // Empty node selection for example <div>|</div>
  10128. // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
  10129. container.innerHTML = '<span>&#xFEFF;</span>';
  10130. marker = container.firstChild;
  10131. tmpRng.moveToElementText(marker);
  10132. tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
  10133. }
  10134. ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  10135. dom.remove(marker);
  10136. }
  10137. }
  10138. // Setup some shorter versions
  10139. startContainer = rng.startContainer;
  10140. startOffset = rng.startOffset;
  10141. endContainer = rng.endContainer;
  10142. endOffset = rng.endOffset;
  10143. ieRng = body.createTextRange();
  10144. // If single element selection then try making a control selection out of it
  10145. if (startContainer == endContainer && startContainer.nodeType == 1) {
  10146. // Trick to place the caret inside an empty block element like <p></p>
  10147. if (startOffset == endOffset && !startContainer.hasChildNodes()) {
  10148. if (startContainer.canHaveHTML) {
  10149. // Check if previous sibling is an empty block if it is then we need to render it
  10150. // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
  10151. // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
  10152. sibling = startContainer.previousSibling;
  10153. if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
  10154. sibling.innerHTML = '&#xFEFF;';
  10155. } else {
  10156. sibling = null;
  10157. }
  10158. startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
  10159. ieRng.moveToElementText(startContainer.lastChild);
  10160. ieRng.select();
  10161. dom.doc.selection.clear();
  10162. startContainer.innerHTML = '';
  10163. if (sibling) {
  10164. sibling.innerHTML = '';
  10165. }
  10166. return;
  10167. } else {
  10168. startOffset = dom.nodeIndex(startContainer);
  10169. startContainer = startContainer.parentNode;
  10170. }
  10171. }
  10172. if (startOffset == endOffset - 1) {
  10173. try {
  10174. ctrlElm = startContainer.childNodes[startOffset];
  10175. ctrlRng = body.createControlRange();
  10176. ctrlRng.addElement(ctrlElm);
  10177. ctrlRng.select();
  10178. // Check if the range produced is on the correct element and is a control range
  10179. // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
  10180. nativeRng = selection.getRng();
  10181. if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
  10182. return;
  10183. }
  10184. } catch (ex) {
  10185. // Ignore
  10186. }
  10187. }
  10188. }
  10189. // Set start/end point of selection
  10190. setEndPoint(true);
  10191. setEndPoint();
  10192. // Select the new range and scroll it into view
  10193. ieRng.select();
  10194. };
  10195. // Expose range method
  10196. this.getRangeAt = getRange;
  10197. }
  10198. return Selection;
  10199. });
  10200. // Included from: js/tinymce/classes/util/VK.js
  10201. /**
  10202. * VK.js
  10203. *
  10204. * Copyright, Moxiecode Systems AB
  10205. * Released under LGPL License.
  10206. *
  10207. * License: http://www.tinymce.com/license
  10208. * Contributing: http://www.tinymce.com/contributing
  10209. */
  10210. /**
  10211. * This file exposes a set of the common KeyCodes for use. Please grow it as needed.
  10212. */
  10213. define("tinymce/util/VK", [
  10214. "tinymce/Env"
  10215. ], function(Env) {
  10216. return {
  10217. BACKSPACE: 8,
  10218. DELETE: 46,
  10219. DOWN: 40,
  10220. ENTER: 13,
  10221. LEFT: 37,
  10222. RIGHT: 39,
  10223. SPACEBAR: 32,
  10224. TAB: 9,
  10225. UP: 38,
  10226. modifierPressed: function(e) {
  10227. return e.shiftKey || e.ctrlKey || e.altKey;
  10228. },
  10229. metaKeyPressed: function(e) {
  10230. // Check if ctrl or meta key is pressed also check if alt is false for Polish users
  10231. return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey;
  10232. }
  10233. };
  10234. });
  10235. // Included from: js/tinymce/classes/dom/ControlSelection.js
  10236. /**
  10237. * ControlSelection.js
  10238. *
  10239. * Copyright, Moxiecode Systems AB
  10240. * Released under LGPL License.
  10241. *
  10242. * License: http://www.tinymce.com/license
  10243. * Contributing: http://www.tinymce.com/contributing
  10244. */
  10245. /**
  10246. * This class handles control selection of elements. Controls are elements
  10247. * that can be resized and needs to be selected as a whole. It adds custom resize handles
  10248. * to all browser engines that support properly disabling the built in resize logic.
  10249. *
  10250. * @class tinymce.dom.ControlSelection
  10251. */
  10252. define("tinymce/dom/ControlSelection", [
  10253. "tinymce/util/VK",
  10254. "tinymce/util/Tools",
  10255. "tinymce/Env"
  10256. ], function(VK, Tools, Env) {
  10257. return function(selection, editor) {
  10258. var dom = editor.dom, each = Tools.each;
  10259. var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent;
  10260. var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
  10261. var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
  10262. // Details about each resize handle how to scale etc
  10263. resizeHandles = {
  10264. // Name: x multiplier, y multiplier, delta size x, delta size y
  10265. n: [0.5, 0, 0, -1],
  10266. e: [1, 0.5, 1, 0],
  10267. s: [0.5, 1, 0, 1],
  10268. w: [0, 0.5, -1, 0],
  10269. nw: [0, 0, -1, -1],
  10270. ne: [1, 0, 1, -1],
  10271. se: [1, 1, 1, 1],
  10272. sw: [0, 1, -1, 1]
  10273. };
  10274. // Add CSS for resize handles, cloned element and selected
  10275. var rootClass = '.mce-content-body';
  10276. editor.contentStyles.push(
  10277. rootClass + ' div.mce-resizehandle {' +
  10278. 'position: absolute;' +
  10279. 'border: 1px solid black;' +
  10280. 'background: #FFF;' +
  10281. 'width: 5px;' +
  10282. 'height: 5px;' +
  10283. 'z-index: 10000' +
  10284. '}' +
  10285. rootClass + ' .mce-resizehandle:hover {' +
  10286. 'background: #000' +
  10287. '}' +
  10288. rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
  10289. 'outline: 1px solid black;' +
  10290. 'resize: none' + // Have been talks about implementing this in browsers
  10291. '}' +
  10292. rootClass + ' .mce-clonedresizable {' +
  10293. 'position: absolute;' +
  10294. (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
  10295. 'opacity: .5;' +
  10296. 'filter: alpha(opacity=50);' +
  10297. 'z-index: 10000' +
  10298. '}'
  10299. );
  10300. function isResizable(elm) {
  10301. var selector = editor.settings.object_resizing;
  10302. if (selector === false || Env.iOS) {
  10303. return false;
  10304. }
  10305. if (typeof selector != 'string') {
  10306. selector = 'table,img,div';
  10307. }
  10308. if (elm.getAttribute('data-mce-resize') === 'false') {
  10309. return false;
  10310. }
  10311. return editor.dom.is(elm, selector);
  10312. }
  10313. function resizeGhostElement(e) {
  10314. var deltaX, deltaY;
  10315. // Calc new width/height
  10316. deltaX = e.screenX - startX;
  10317. deltaY = e.screenY - startY;
  10318. // Calc new size
  10319. width = deltaX * selectedHandle[2] + startW;
  10320. height = deltaY * selectedHandle[3] + startH;
  10321. // Never scale down lower than 5 pixels
  10322. width = width < 5 ? 5 : width;
  10323. height = height < 5 ? 5 : height;
  10324. // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
  10325. if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
  10326. width = Math.round(height / ratio);
  10327. height = Math.round(width * ratio);
  10328. }
  10329. // Update ghost size
  10330. dom.setStyles(selectedElmGhost, {
  10331. width: width,
  10332. height: height
  10333. });
  10334. // Update ghost X position if needed
  10335. if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
  10336. dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
  10337. }
  10338. // Update ghost Y position if needed
  10339. if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
  10340. dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
  10341. }
  10342. if (!resizeStarted) {
  10343. editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
  10344. resizeStarted = true;
  10345. }
  10346. }
  10347. function endGhostResize() {
  10348. resizeStarted = false;
  10349. function setSizeProp(name, value) {
  10350. if (value) {
  10351. // Resize by using style or attribute
  10352. if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
  10353. dom.setStyle(selectedElm, name, value);
  10354. } else {
  10355. dom.setAttrib(selectedElm, name, value);
  10356. }
  10357. }
  10358. }
  10359. // Set width/height properties
  10360. setSizeProp('width', width);
  10361. setSizeProp('height', height);
  10362. dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
  10363. dom.unbind(editableDoc, 'mouseup', endGhostResize);
  10364. if (rootDocument != editableDoc) {
  10365. dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
  10366. dom.unbind(rootDocument, 'mouseup', endGhostResize);
  10367. }
  10368. // Remove ghost and update resize handle positions
  10369. dom.remove(selectedElmGhost);
  10370. if (!isIE || selectedElm.nodeName == "TABLE") {
  10371. showResizeRect(selectedElm);
  10372. }
  10373. editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
  10374. editor.nodeChanged();
  10375. }
  10376. function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
  10377. var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody();
  10378. unbindResizeHandleEvents();
  10379. // Get position and size of target
  10380. position = dom.getPos(targetElm, offsetParent);
  10381. selectedElmX = position.x;
  10382. selectedElmY = position.y;
  10383. rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
  10384. targetWidth = rect.width || (rect.right - rect.left);
  10385. targetHeight = rect.height || (rect.bottom - rect.top);
  10386. // Reset width/height if user selects a new image/table
  10387. if (selectedElm != targetElm) {
  10388. detachResizeStartListener();
  10389. selectedElm = targetElm;
  10390. width = height = 0;
  10391. }
  10392. // Makes it possible to disable resizing
  10393. e = editor.fire('ObjectSelected', {target: targetElm});
  10394. if (isResizable(targetElm) && !e.isDefaultPrevented()) {
  10395. each(resizeHandles, function(handle, name) {
  10396. var handleElm, handlerContainerElm;
  10397. function startDrag(e) {
  10398. startX = e.screenX;
  10399. startY = e.screenY;
  10400. startW = selectedElm.clientWidth;
  10401. startH = selectedElm.clientHeight;
  10402. ratio = startH / startW;
  10403. selectedHandle = handle;
  10404. selectedElmGhost = selectedElm.cloneNode(true);
  10405. dom.addClass(selectedElmGhost, 'mce-clonedresizable');
  10406. selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
  10407. selectedElmGhost.unSelectabe = true;
  10408. dom.setStyles(selectedElmGhost, {
  10409. left: selectedElmX,
  10410. top: selectedElmY,
  10411. margin: 0
  10412. });
  10413. selectedElmGhost.removeAttribute('data-mce-selected');
  10414. editor.getBody().appendChild(selectedElmGhost);
  10415. dom.bind(editableDoc, 'mousemove', resizeGhostElement);
  10416. dom.bind(editableDoc, 'mouseup', endGhostResize);
  10417. if (rootDocument != editableDoc) {
  10418. dom.bind(rootDocument, 'mousemove', resizeGhostElement);
  10419. dom.bind(rootDocument, 'mouseup', endGhostResize);
  10420. }
  10421. }
  10422. if (mouseDownHandleName) {
  10423. // Drag started by IE native resizestart
  10424. if (name == mouseDownHandleName) {
  10425. startDrag(mouseDownEvent);
  10426. }
  10427. return;
  10428. }
  10429. // Get existing or render resize handle
  10430. handleElm = dom.get('mceResizeHandle' + name);
  10431. if (!handleElm) {
  10432. handlerContainerElm = editor.getBody();
  10433. handleElm = dom.add(handlerContainerElm, 'div', {
  10434. id: 'mceResizeHandle' + name,
  10435. 'data-mce-bogus': true,
  10436. 'class': 'mce-resizehandle',
  10437. unselectable: true,
  10438. style: 'cursor:' + name + '-resize; margin:0; padding:0'
  10439. });
  10440. // Hides IE move layer cursor
  10441. // If we set it on Chrome we get this wounderful bug: #6725
  10442. if (Env.ie) {
  10443. handleElm.contentEditable = false;
  10444. }
  10445. } else {
  10446. dom.show(handleElm);
  10447. }
  10448. if (!handle.elm) {
  10449. dom.bind(handleElm, 'mousedown', function(e) {
  10450. e.stopImmediatePropagation();
  10451. e.preventDefault();
  10452. startDrag(e);
  10453. });
  10454. handle.elm = handleElm;
  10455. }
  10456. /*
  10457. var halfHandleW = handleElm.offsetWidth / 2;
  10458. var halfHandleH = handleElm.offsetHeight / 2;
  10459. // Position element
  10460. dom.setStyles(handleElm, {
  10461. left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)),
  10462. top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH))
  10463. });
  10464. */
  10465. // Position element
  10466. dom.setStyles(handleElm, {
  10467. left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
  10468. top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
  10469. });
  10470. });
  10471. } else {
  10472. hideResizeRect();
  10473. }
  10474. selectedElm.setAttribute('data-mce-selected', '1');
  10475. }
  10476. function hideResizeRect() {
  10477. var name, handleElm;
  10478. unbindResizeHandleEvents();
  10479. if (selectedElm) {
  10480. selectedElm.removeAttribute('data-mce-selected');
  10481. }
  10482. for (name in resizeHandles) {
  10483. handleElm = dom.get('mceResizeHandle' + name);
  10484. if (handleElm) {
  10485. dom.unbind(handleElm);
  10486. dom.remove(handleElm);
  10487. }
  10488. }
  10489. }
  10490. function updateResizeRect(e) {
  10491. var controlElm;
  10492. function isChildOrEqual(node, parent) {
  10493. if (node) {
  10494. do {
  10495. if (node === parent) {
  10496. return true;
  10497. }
  10498. } while ((node = node.parentNode));
  10499. }
  10500. }
  10501. // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
  10502. each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
  10503. img.removeAttribute('data-mce-selected');
  10504. });
  10505. controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
  10506. controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
  10507. if (isChildOrEqual(controlElm, editor.getBody())) {
  10508. disableGeckoResize();
  10509. if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
  10510. if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) {
  10511. showResizeRect(controlElm);
  10512. return;
  10513. }
  10514. }
  10515. }
  10516. hideResizeRect();
  10517. }
  10518. function attachEvent(elm, name, func) {
  10519. if (elm && elm.attachEvent) {
  10520. elm.attachEvent('on' + name, func);
  10521. }
  10522. }
  10523. function detachEvent(elm, name, func) {
  10524. if (elm && elm.detachEvent) {
  10525. elm.detachEvent('on' + name, func);
  10526. }
  10527. }
  10528. function resizeNativeStart(e) {
  10529. var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
  10530. pos = target.getBoundingClientRect();
  10531. relativeX = lastMouseDownEvent.clientX - pos.left;
  10532. relativeY = lastMouseDownEvent.clientY - pos.top;
  10533. // Figure out what corner we are draging on
  10534. for (name in resizeHandles) {
  10535. corner = resizeHandles[name];
  10536. cornerX = target.offsetWidth * corner[0];
  10537. cornerY = target.offsetHeight * corner[1];
  10538. if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) {
  10539. selectedHandle = corner;
  10540. break;
  10541. }
  10542. }
  10543. // Remove native selection and let the magic begin
  10544. resizeStarted = true;
  10545. editor.getDoc().selection.empty();
  10546. showResizeRect(target, name, lastMouseDownEvent);
  10547. }
  10548. function nativeControlSelect(e) {
  10549. var target = e.srcElement;
  10550. if (target != selectedElm) {
  10551. detachResizeStartListener();
  10552. if (target.id.indexOf('mceResizeHandle') === 0) {
  10553. e.returnValue = false;
  10554. return;
  10555. }
  10556. if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
  10557. hideResizeRect();
  10558. selectedElm = target;
  10559. attachEvent(target, 'resizestart', resizeNativeStart);
  10560. }
  10561. }
  10562. }
  10563. function detachResizeStartListener() {
  10564. detachEvent(selectedElm, 'resizestart', resizeNativeStart);
  10565. }
  10566. function unbindResizeHandleEvents() {
  10567. for (var name in resizeHandles) {
  10568. var handle = resizeHandles[name];
  10569. if (handle.elm) {
  10570. dom.unbind(handle.elm);
  10571. delete handle.elm;
  10572. }
  10573. }
  10574. }
  10575. function disableGeckoResize() {
  10576. try {
  10577. // Disable object resizing on Gecko
  10578. editor.getDoc().execCommand('enableObjectResizing', false, false);
  10579. } catch (ex) {
  10580. // Ignore
  10581. }
  10582. }
  10583. function controlSelect(elm) {
  10584. var ctrlRng;
  10585. if (!isIE) {
  10586. return;
  10587. }
  10588. ctrlRng = editableDoc.body.createControlRange();
  10589. try {
  10590. ctrlRng.addElement(elm);
  10591. ctrlRng.select();
  10592. return true;
  10593. } catch (ex) {
  10594. // Ignore since the element can't be control selected for example a P tag
  10595. }
  10596. }
  10597. editor.on('init', function() {
  10598. if (isIE) {
  10599. // Hide the resize rect on resize and reselect the image
  10600. editor.on('ObjectResized', function(e) {
  10601. if (e.target.nodeName != 'TABLE') {
  10602. hideResizeRect();
  10603. controlSelect(e.target);
  10604. }
  10605. });
  10606. attachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
  10607. editor.on('mousedown', function(e) {
  10608. lastMouseDownEvent = e;
  10609. });
  10610. } else {
  10611. disableGeckoResize();
  10612. if (Env.ie >= 11) {
  10613. // TODO: Drag/drop doesn't work
  10614. editor.on('mouseup', function(e) {
  10615. var nodeName = e.target.nodeName;
  10616. if (/^(TABLE|IMG|HR)$/.test(nodeName)) {
  10617. editor.selection.select(e.target, nodeName == 'TABLE');
  10618. editor.nodeChanged();
  10619. }
  10620. });
  10621. editor.dom.bind(editor.getBody(), 'mscontrolselect', function(e) {
  10622. if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
  10623. e.preventDefault();
  10624. // This moves the selection from being a control selection to a text like selection like in WebKit #6753
  10625. // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
  10626. if (e.target.tagName == 'IMG') {
  10627. window.setTimeout(function() {
  10628. editor.selection.select(e.target);
  10629. }, 0);
  10630. }
  10631. }
  10632. });
  10633. }
  10634. }
  10635. editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect);
  10636. // Update resize rect while typing in a table
  10637. editor.on('keydown keyup', function(e) {
  10638. if (selectedElm && selectedElm.nodeName == "TABLE") {
  10639. updateResizeRect(e);
  10640. }
  10641. });
  10642. // Hide rect on focusout since it would float on top of windows otherwise
  10643. //editor.on('focusout', hideResizeRect);
  10644. });
  10645. editor.on('remove', unbindResizeHandleEvents);
  10646. function destroy() {
  10647. selectedElm = selectedElmGhost = null;
  10648. if (isIE) {
  10649. detachResizeStartListener();
  10650. detachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
  10651. }
  10652. }
  10653. return {
  10654. isResizable: isResizable,
  10655. showResizeRect: showResizeRect,
  10656. hideResizeRect: hideResizeRect,
  10657. updateResizeRect: updateResizeRect,
  10658. controlSelect: controlSelect,
  10659. destroy: destroy
  10660. };
  10661. };
  10662. });
  10663. // Included from: js/tinymce/classes/dom/RangeUtils.js
  10664. /**
  10665. * Range.js
  10666. *
  10667. * Copyright, Moxiecode Systems AB
  10668. * Released under LGPL License.
  10669. *
  10670. * License: http://www.tinymce.com/license
  10671. * Contributing: http://www.tinymce.com/contributing
  10672. */
  10673. /**
  10674. * RangeUtils
  10675. *
  10676. * @class tinymce.dom.RangeUtils
  10677. * @private
  10678. */
  10679. define("tinymce/dom/RangeUtils", [
  10680. "tinymce/util/Tools",
  10681. "tinymce/dom/TreeWalker"
  10682. ], function(Tools, TreeWalker) {
  10683. var each = Tools.each;
  10684. function RangeUtils(dom) {
  10685. /**
  10686. * Walks the specified range like object and executes the callback for each sibling collection it finds.
  10687. *
  10688. * @method walk
  10689. * @param {Object} rng Range like object.
  10690. * @param {function} callback Callback function to execute for each sibling collection.
  10691. */
  10692. this.walk = function(rng, callback) {
  10693. var startContainer = rng.startContainer,
  10694. startOffset = rng.startOffset,
  10695. endContainer = rng.endContainer,
  10696. endOffset = rng.endOffset,
  10697. ancestor, startPoint,
  10698. endPoint, node, parent, siblings, nodes;
  10699. // Handle table cell selection the table plugin enables
  10700. // you to fake select table cells and perform formatting actions on them
  10701. nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
  10702. if (nodes.length > 0) {
  10703. each(nodes, function(node) {
  10704. callback([node]);
  10705. });
  10706. return;
  10707. }
  10708. /**
  10709. * Excludes start/end text node if they are out side the range
  10710. *
  10711. * @private
  10712. * @param {Array} nodes Nodes to exclude items from.
  10713. * @return {Array} Array with nodes excluding the start/end container if needed.
  10714. */
  10715. function exclude(nodes) {
  10716. var node;
  10717. // First node is excluded
  10718. node = nodes[0];
  10719. if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
  10720. nodes.splice(0, 1);
  10721. }
  10722. // Last node is excluded
  10723. node = nodes[nodes.length - 1];
  10724. if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
  10725. nodes.splice(nodes.length - 1, 1);
  10726. }
  10727. return nodes;
  10728. }
  10729. /**
  10730. * Collects siblings
  10731. *
  10732. * @private
  10733. * @param {Node} node Node to collect siblings from.
  10734. * @param {String} name Name of the sibling to check for.
  10735. * @return {Array} Array of collected siblings.
  10736. */
  10737. function collectSiblings(node, name, end_node) {
  10738. var siblings = [];
  10739. for (; node && node != end_node; node = node[name]) {
  10740. siblings.push(node);
  10741. }
  10742. return siblings;
  10743. }
  10744. /**
  10745. * Find an end point this is the node just before the common ancestor root.
  10746. *
  10747. * @private
  10748. * @param {Node} node Node to start at.
  10749. * @param {Node} root Root/ancestor element to stop just before.
  10750. * @return {Node} Node just before the root element.
  10751. */
  10752. function findEndPoint(node, root) {
  10753. do {
  10754. if (node.parentNode == root) {
  10755. return node;
  10756. }
  10757. node = node.parentNode;
  10758. } while(node);
  10759. }
  10760. function walkBoundary(start_node, end_node, next) {
  10761. var siblingName = next ? 'nextSibling' : 'previousSibling';
  10762. for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
  10763. parent = node.parentNode;
  10764. siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
  10765. if (siblings.length) {
  10766. if (!next) {
  10767. siblings.reverse();
  10768. }
  10769. callback(exclude(siblings));
  10770. }
  10771. }
  10772. }
  10773. // If index based start position then resolve it
  10774. if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  10775. startContainer = startContainer.childNodes[startOffset];
  10776. }
  10777. // If index based end position then resolve it
  10778. if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  10779. endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
  10780. }
  10781. // Same container
  10782. if (startContainer == endContainer) {
  10783. return callback(exclude([startContainer]));
  10784. }
  10785. // Find common ancestor and end points
  10786. ancestor = dom.findCommonAncestor(startContainer, endContainer);
  10787. // Process left side
  10788. for (node = startContainer; node; node = node.parentNode) {
  10789. if (node === endContainer) {
  10790. return walkBoundary(startContainer, ancestor, true);
  10791. }
  10792. if (node === ancestor) {
  10793. break;
  10794. }
  10795. }
  10796. // Process right side
  10797. for (node = endContainer; node; node = node.parentNode) {
  10798. if (node === startContainer) {
  10799. return walkBoundary(endContainer, ancestor);
  10800. }
  10801. if (node === ancestor) {
  10802. break;
  10803. }
  10804. }
  10805. // Find start/end point
  10806. startPoint = findEndPoint(startContainer, ancestor) || startContainer;
  10807. endPoint = findEndPoint(endContainer, ancestor) || endContainer;
  10808. // Walk left leaf
  10809. walkBoundary(startContainer, startPoint, true);
  10810. // Walk the middle from start to end point
  10811. siblings = collectSiblings(
  10812. startPoint == startContainer ? startPoint : startPoint.nextSibling,
  10813. 'nextSibling',
  10814. endPoint == endContainer ? endPoint.nextSibling : endPoint
  10815. );
  10816. if (siblings.length) {
  10817. callback(exclude(siblings));
  10818. }
  10819. // Walk right leaf
  10820. walkBoundary(endContainer, endPoint);
  10821. };
  10822. /**
  10823. * Splits the specified range at it's start/end points.
  10824. *
  10825. * @private
  10826. * @param {Range/RangeObject} rng Range to split.
  10827. * @return {Object} Range position object.
  10828. */
  10829. this.split = function(rng) {
  10830. var startContainer = rng.startContainer,
  10831. startOffset = rng.startOffset,
  10832. endContainer = rng.endContainer,
  10833. endOffset = rng.endOffset;
  10834. function splitText(node, offset) {
  10835. return node.splitText(offset);
  10836. }
  10837. // Handle single text node
  10838. if (startContainer == endContainer && startContainer.nodeType == 3) {
  10839. if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  10840. endContainer = splitText(startContainer, startOffset);
  10841. startContainer = endContainer.previousSibling;
  10842. if (endOffset > startOffset) {
  10843. endOffset = endOffset - startOffset;
  10844. startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
  10845. endOffset = endContainer.nodeValue.length;
  10846. startOffset = 0;
  10847. } else {
  10848. endOffset = 0;
  10849. }
  10850. }
  10851. } else {
  10852. // Split startContainer text node if needed
  10853. if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  10854. startContainer = splitText(startContainer, startOffset);
  10855. startOffset = 0;
  10856. }
  10857. // Split endContainer text node if needed
  10858. if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
  10859. endContainer = splitText(endContainer, endOffset).previousSibling;
  10860. endOffset = endContainer.nodeValue.length;
  10861. }
  10862. }
  10863. return {
  10864. startContainer: startContainer,
  10865. startOffset: startOffset,
  10866. endContainer: endContainer,
  10867. endOffset: endOffset
  10868. };
  10869. };
  10870. /**
  10871. * Normalizes the specified range by finding the closest best suitable caret location.
  10872. *
  10873. * @private
  10874. * @param {Range} rng Range to normalize.
  10875. * @return {Boolean} True/false if the specified range was normalized or not.
  10876. */
  10877. this.normalize = function(rng) {
  10878. var normalized, collapsed;
  10879. function normalizeEndPoint(start) {
  10880. var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
  10881. var directionLeft, isAfterNode;
  10882. function hasBrBeforeAfter(node, left) {
  10883. var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
  10884. while ((node = walker[left ? 'prev' : 'next']())) {
  10885. if (node.nodeName === "BR") {
  10886. return true;
  10887. }
  10888. }
  10889. }
  10890. function isPrevNode(node, name) {
  10891. return node.previousSibling && node.previousSibling.nodeName == name;
  10892. }
  10893. // Walks the dom left/right to find a suitable text node to move the endpoint into
  10894. // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
  10895. function findTextNodeRelative(left, startNode) {
  10896. var walker, lastInlineElement, parentBlockContainer;
  10897. startNode = startNode || container;
  10898. parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
  10899. // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
  10900. // This: <p><br>|</p> becomes <p>|<br></p>
  10901. if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
  10902. container = startNode.parentNode;
  10903. offset = dom.nodeIndex(startNode);
  10904. normalized = true;
  10905. return;
  10906. }
  10907. // Walk left until we hit a text node we can move to or a block/br/img
  10908. walker = new TreeWalker(startNode, parentBlockContainer);
  10909. while ((node = walker[left ? 'prev' : 'next']())) {
  10910. // Break if we hit a non content editable node
  10911. if (dom.getContentEditableParent(node) === "false") {
  10912. return;
  10913. }
  10914. // Found text node that has a length
  10915. if (node.nodeType === 3 && node.nodeValue.length > 0) {
  10916. container = node;
  10917. offset = left ? node.nodeValue.length : 0;
  10918. normalized = true;
  10919. return;
  10920. }
  10921. // Break if we find a block or a BR/IMG/INPUT etc
  10922. if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  10923. return;
  10924. }
  10925. lastInlineElement = node;
  10926. }
  10927. // Only fetch the last inline element when in caret mode for now
  10928. if (collapsed && lastInlineElement) {
  10929. container = lastInlineElement;
  10930. normalized = true;
  10931. offset = 0;
  10932. }
  10933. }
  10934. container = rng[(start ? 'start' : 'end') + 'Container'];
  10935. offset = rng[(start ? 'start' : 'end') + 'Offset'];
  10936. isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
  10937. nonEmptyElementsMap = dom.schema.getNonEmptyElements();
  10938. directionLeft = start;
  10939. if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
  10940. directionLeft = false;
  10941. }
  10942. // If the container is a document move it to the body element
  10943. if (container.nodeType === 9) {
  10944. container = dom.getRoot();
  10945. offset = 0;
  10946. }
  10947. // If the container is body try move it into the closest text node or position
  10948. if (container === body) {
  10949. // If start is before/after a image, table etc
  10950. if (directionLeft) {
  10951. node = container.childNodes[offset > 0 ? offset - 1 : 0];
  10952. if (node) {
  10953. if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
  10954. return;
  10955. }
  10956. }
  10957. }
  10958. // Resolve the index
  10959. if (container.hasChildNodes()) {
  10960. offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
  10961. container = container.childNodes[offset];
  10962. offset = 0;
  10963. // Don't walk into elements that doesn't have any child nodes like a IMG
  10964. if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
  10965. // Walk the DOM to find a text node to place the caret at or a BR
  10966. node = container;
  10967. walker = new TreeWalker(container, body);
  10968. do {
  10969. // Found a text node use that position
  10970. if (node.nodeType === 3 && node.nodeValue.length > 0) {
  10971. offset = directionLeft ? 0 : node.nodeValue.length;
  10972. container = node;
  10973. normalized = true;
  10974. break;
  10975. }
  10976. // Found a BR/IMG element that we can place the caret before
  10977. if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  10978. offset = dom.nodeIndex(node);
  10979. container = node.parentNode;
  10980. // Put caret after image when moving the end point
  10981. if (node.nodeName == "IMG" && !directionLeft) {
  10982. offset++;
  10983. }
  10984. normalized = true;
  10985. break;
  10986. }
  10987. } while ((node = (directionLeft ? walker.next() : walker.prev())));
  10988. }
  10989. }
  10990. }
  10991. // Lean the caret to the left if possible
  10992. if (collapsed) {
  10993. // So this: <b>x</b><i>|x</i>
  10994. // Becomes: <b>x|</b><i>x</i>
  10995. // Seems that only gecko has issues with this
  10996. if (container.nodeType === 3 && offset === 0) {
  10997. findTextNodeRelative(true);
  10998. }
  10999. // Lean left into empty inline elements when the caret is before a BR
  11000. // So this: <i><b></b><i>|<br></i>
  11001. // Becomes: <i><b>|</b><i><br></i>
  11002. // Seems that only gecko has issues with this.
  11003. // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
  11004. if (container.nodeType === 1) {
  11005. node = container.childNodes[offset];
  11006. // Offset is after the containers last child
  11007. // then use the previous child for normalization
  11008. if (!node) {
  11009. node = container.childNodes[offset - 1];
  11010. }
  11011. if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
  11012. !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
  11013. findTextNodeRelative(true, node);
  11014. }
  11015. }
  11016. }
  11017. // Lean the start of the selection right if possible
  11018. // So this: x[<b>x]</b>
  11019. // Becomes: x<b>[x]</b>
  11020. if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
  11021. findTextNodeRelative(false);
  11022. }
  11023. // Set endpoint if it was normalized
  11024. if (normalized) {
  11025. rng['set' + (start ? 'Start' : 'End')](container, offset);
  11026. }
  11027. }
  11028. collapsed = rng.collapsed;
  11029. normalizeEndPoint(true);
  11030. if (!collapsed) {
  11031. normalizeEndPoint();
  11032. }
  11033. // If it was collapsed then make sure it still is
  11034. if (normalized && collapsed) {
  11035. rng.collapse(true);
  11036. }
  11037. return normalized;
  11038. };
  11039. }
  11040. /**
  11041. * Compares two ranges and checks if they are equal.
  11042. *
  11043. * @static
  11044. * @method compareRanges
  11045. * @param {DOMRange} rng1 First range to compare.
  11046. * @param {DOMRange} rng2 First range to compare.
  11047. * @return {Boolean} true/false if the ranges are equal.
  11048. */
  11049. RangeUtils.compareRanges = function(rng1, rng2) {
  11050. if (rng1 && rng2) {
  11051. // Compare native IE ranges
  11052. if (rng1.item || rng1.duplicate) {
  11053. // Both are control ranges and the selected element matches
  11054. if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
  11055. return true;
  11056. }
  11057. // Both are text ranges and the range matches
  11058. if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
  11059. return true;
  11060. }
  11061. } else {
  11062. // Compare w3c ranges
  11063. return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
  11064. }
  11065. }
  11066. return false;
  11067. };
  11068. return RangeUtils;
  11069. });
  11070. // Included from: js/tinymce/classes/dom/Selection.js
  11071. /**
  11072. * Selection.js
  11073. *
  11074. * Copyright, Moxiecode Systems AB
  11075. * Released under LGPL License.
  11076. *
  11077. * License: http://www.tinymce.com/license
  11078. * Contributing: http://www.tinymce.com/contributing
  11079. */
  11080. /**
  11081. * This class handles text and control selection it's an crossbrowser utility class.
  11082. * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
  11083. *
  11084. * @class tinymce.dom.Selection
  11085. * @example
  11086. * // Getting the currently selected node for the active editor
  11087. * alert(tinymce.activeEditor.selection.getNode().nodeName);
  11088. */
  11089. define("tinymce/dom/Selection", [
  11090. "tinymce/dom/TreeWalker",
  11091. "tinymce/dom/TridentSelection",
  11092. "tinymce/dom/ControlSelection",
  11093. "tinymce/dom/RangeUtils",
  11094. "tinymce/Env",
  11095. "tinymce/util/Tools"
  11096. ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, Env, Tools) {
  11097. var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
  11098. var isIE = Env.ie, isOpera = Env.opera;
  11099. /**
  11100. * Constructs a new selection instance.
  11101. *
  11102. * @constructor
  11103. * @method Selection
  11104. * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
  11105. * @param {Window} win Window to bind the selection object to.
  11106. * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
  11107. */
  11108. function Selection(dom, win, serializer, editor) {
  11109. var self = this;
  11110. self.dom = dom;
  11111. self.win = win;
  11112. self.serializer = serializer;
  11113. self.editor = editor;
  11114. self.controlSelection = new ControlSelection(self, editor);
  11115. // No W3C Range support
  11116. if (!self.win.getSelection) {
  11117. self.tridentSel = new TridentSelection(self);
  11118. }
  11119. }
  11120. Selection.prototype = {
  11121. /**
  11122. * Move the selection cursor range to the specified node and offset.
  11123. * If there is no node specified it will move it to the first suitable location within the body.
  11124. *
  11125. * @method setCursorLocation
  11126. * @param {Node} node Optional node to put the cursor in.
  11127. * @param {Number} offset Optional offset from the start of the node to put the cursor at.
  11128. */
  11129. setCursorLocation: function(node, offset) {
  11130. var self = this, rng = self.dom.createRng();
  11131. if (!node) {
  11132. self._moveEndPoint(rng, self.editor.getBody(), true);
  11133. self.setRng(rng);
  11134. } else {
  11135. rng.setStart(node, offset);
  11136. rng.setEnd(node, offset);
  11137. self.setRng(rng);
  11138. self.collapse(false);
  11139. }
  11140. },
  11141. /**
  11142. * Returns the selected contents using the DOM serializer passed in to this class.
  11143. *
  11144. * @method getContent
  11145. * @param {Object} s Optional settings class with for example output format text or html.
  11146. * @return {String} Selected contents in for example HTML format.
  11147. * @example
  11148. * // Alerts the currently selected contents
  11149. * alert(tinymce.activeEditor.selection.getContent());
  11150. *
  11151. * // Alerts the currently selected contents as plain text
  11152. * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
  11153. */
  11154. getContent: function(args) {
  11155. var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
  11156. var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
  11157. args = args || {};
  11158. whiteSpaceBefore = whiteSpaceAfter = '';
  11159. args.get = true;
  11160. args.format = args.format || 'html';
  11161. args.selection = true;
  11162. self.editor.fire('BeforeGetContent', args);
  11163. if (args.format == 'text') {
  11164. return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
  11165. }
  11166. if (rng.cloneContents) {
  11167. fragment = rng.cloneContents();
  11168. if (fragment) {
  11169. tmpElm.appendChild(fragment);
  11170. }
  11171. } else if (rng.item !== undefined || rng.htmlText !== undefined) {
  11172. // IE will produce invalid markup if elements are present that
  11173. // it doesn't understand like custom elements or HTML5 elements.
  11174. // Adding a BR in front of the contents and then remoiving it seems to fix it though.
  11175. tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
  11176. tmpElm.removeChild(tmpElm.firstChild);
  11177. } else {
  11178. tmpElm.innerHTML = rng.toString();
  11179. }
  11180. // Keep whitespace before and after
  11181. if (/^\s/.test(tmpElm.innerHTML)) {
  11182. whiteSpaceBefore = ' ';
  11183. }
  11184. if (/\s+$/.test(tmpElm.innerHTML)) {
  11185. whiteSpaceAfter = ' ';
  11186. }
  11187. args.getInner = true;
  11188. args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
  11189. self.editor.fire('GetContent', args);
  11190. return args.content;
  11191. },
  11192. /**
  11193. * Sets the current selection to the specified content. If any contents is selected it will be replaced
  11194. * with the contents passed in to this function. If there is no selection the contents will be inserted
  11195. * where the caret is placed in the editor/page.
  11196. *
  11197. * @method setContent
  11198. * @param {String} content HTML contents to set could also be other formats depending on settings.
  11199. * @param {Object} args Optional settings object with for example data format.
  11200. * @example
  11201. * // Inserts some HTML contents at the current selection
  11202. * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
  11203. */
  11204. setContent: function(content, args) {
  11205. var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
  11206. args = args || {format: 'html'};
  11207. args.set = true;
  11208. args.selection = true;
  11209. content = args.content = content;
  11210. // Dispatch before set content event
  11211. if (!args.no_events) {
  11212. self.editor.fire('BeforeSetContent', args);
  11213. }
  11214. content = args.content;
  11215. if (rng.insertNode) {
  11216. // Make caret marker since insertNode places the caret in the beginning of text after insert
  11217. content += '<span id="__caret">_</span>';
  11218. // Delete and insert new node
  11219. if (rng.startContainer == doc && rng.endContainer == doc) {
  11220. // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
  11221. doc.body.innerHTML = content;
  11222. } else {
  11223. rng.deleteContents();
  11224. if (doc.body.childNodes.length === 0) {
  11225. doc.body.innerHTML = content;
  11226. } else {
  11227. // createContextualFragment doesn't exists in IE 9 DOMRanges
  11228. if (rng.createContextualFragment) {
  11229. rng.insertNode(rng.createContextualFragment(content));
  11230. } else {
  11231. // Fake createContextualFragment call in IE 9
  11232. frag = doc.createDocumentFragment();
  11233. temp = doc.createElement('div');
  11234. frag.appendChild(temp);
  11235. temp.outerHTML = content;
  11236. rng.insertNode(frag);
  11237. }
  11238. }
  11239. }
  11240. // Move to caret marker
  11241. caretNode = self.dom.get('__caret');
  11242. // Make sure we wrap it compleatly, Opera fails with a simple select call
  11243. rng = doc.createRange();
  11244. rng.setStartBefore(caretNode);
  11245. rng.setEndBefore(caretNode);
  11246. self.setRng(rng);
  11247. // Remove the caret position
  11248. self.dom.remove('__caret');
  11249. try {
  11250. self.setRng(rng);
  11251. } catch (ex) {
  11252. // Might fail on Opera for some odd reason
  11253. }
  11254. } else {
  11255. if (rng.item) {
  11256. // Delete content and get caret text selection
  11257. doc.execCommand('Delete', false, null);
  11258. rng = self.getRng();
  11259. }
  11260. // Explorer removes spaces from the beginning of pasted contents
  11261. if (/^\s+/.test(content)) {
  11262. rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
  11263. self.dom.remove('__mce_tmp');
  11264. } else {
  11265. rng.pasteHTML(content);
  11266. }
  11267. }
  11268. // Dispatch set content event
  11269. if (!args.no_events) {
  11270. self.editor.fire('SetContent', args);
  11271. }
  11272. },
  11273. /**
  11274. * Returns the start element of a selection range. If the start is in a text
  11275. * node the parent element will be returned.
  11276. *
  11277. * @method getStart
  11278. * @return {Element} Start element of selection range.
  11279. */
  11280. getStart: function() {
  11281. var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
  11282. if (rng.duplicate || rng.item) {
  11283. // Control selection, return first item
  11284. if (rng.item) {
  11285. return rng.item(0);
  11286. }
  11287. // Get start element
  11288. checkRng = rng.duplicate();
  11289. checkRng.collapse(1);
  11290. startElement = checkRng.parentElement();
  11291. if (startElement.ownerDocument !== self.dom.doc) {
  11292. startElement = self.dom.getRoot();
  11293. }
  11294. // Check if range parent is inside the start element, then return the inner parent element
  11295. // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
  11296. parentElement = node = rng.parentElement();
  11297. while ((node = node.parentNode)) {
  11298. if (node == startElement) {
  11299. startElement = parentElement;
  11300. break;
  11301. }
  11302. }
  11303. return startElement;
  11304. } else {
  11305. startElement = rng.startContainer;
  11306. if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
  11307. startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
  11308. }
  11309. if (startElement && startElement.nodeType == 3) {
  11310. return startElement.parentNode;
  11311. }
  11312. return startElement;
  11313. }
  11314. },
  11315. /**
  11316. * Returns the end element of a selection range. If the end is in a text
  11317. * node the parent element will be returned.
  11318. *
  11319. * @method getEnd
  11320. * @return {Element} End element of selection range.
  11321. */
  11322. getEnd: function() {
  11323. var self = this, rng = self.getRng(), endElement, endOffset;
  11324. if (rng.duplicate || rng.item) {
  11325. if (rng.item) {
  11326. return rng.item(0);
  11327. }
  11328. rng = rng.duplicate();
  11329. rng.collapse(0);
  11330. endElement = rng.parentElement();
  11331. if (endElement.ownerDocument !== self.dom.doc) {
  11332. endElement = self.dom.getRoot();
  11333. }
  11334. if (endElement && endElement.nodeName == 'BODY') {
  11335. return endElement.lastChild || endElement;
  11336. }
  11337. return endElement;
  11338. } else {
  11339. endElement = rng.endContainer;
  11340. endOffset = rng.endOffset;
  11341. if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
  11342. endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
  11343. }
  11344. if (endElement && endElement.nodeType == 3) {
  11345. return endElement.parentNode;
  11346. }
  11347. return endElement;
  11348. }
  11349. },
  11350. /**
  11351. * Returns a bookmark location for the current selection. This bookmark object
  11352. * can then be used to restore the selection after some content modification to the document.
  11353. *
  11354. * @method getBookmark
  11355. * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
  11356. * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
  11357. * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
  11358. * @example
  11359. * // Stores a bookmark of the current selection
  11360. * var bm = tinymce.activeEditor.selection.getBookmark();
  11361. *
  11362. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  11363. *
  11364. * // Restore the selection bookmark
  11365. * tinymce.activeEditor.selection.moveToBookmark(bm);
  11366. */
  11367. getBookmark: function(type, normalized) {
  11368. var self = this, dom = self.dom, rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
  11369. function findIndex(name, element) {
  11370. var index = 0;
  11371. each(dom.select(name), function(node, i) {
  11372. if (node == element) {
  11373. index = i;
  11374. }
  11375. });
  11376. return index;
  11377. }
  11378. function normalizeTableCellSelection(rng) {
  11379. function moveEndPoint(start) {
  11380. var container, offset, childNodes, prefix = start ? 'start' : 'end';
  11381. container = rng[prefix + 'Container'];
  11382. offset = rng[prefix + 'Offset'];
  11383. if (container.nodeType == 1 && container.nodeName == "TR") {
  11384. childNodes = container.childNodes;
  11385. container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
  11386. if (container) {
  11387. offset = start ? 0 : container.childNodes.length;
  11388. rng['set' + (start ? 'Start' : 'End')](container, offset);
  11389. }
  11390. }
  11391. }
  11392. moveEndPoint(true);
  11393. moveEndPoint();
  11394. return rng;
  11395. }
  11396. function getLocation() {
  11397. var rng = self.getRng(true), root = dom.getRoot(), bookmark = {};
  11398. function getPoint(rng, start) {
  11399. var container = rng[start ? 'startContainer' : 'endContainer'],
  11400. offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
  11401. if (container.nodeType == 3) {
  11402. if (normalized) {
  11403. for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
  11404. offset += node.nodeValue.length;
  11405. }
  11406. }
  11407. point.push(offset);
  11408. } else {
  11409. childNodes = container.childNodes;
  11410. if (offset >= childNodes.length && childNodes.length) {
  11411. after = 1;
  11412. offset = Math.max(0, childNodes.length - 1);
  11413. }
  11414. point.push(self.dom.nodeIndex(childNodes[offset], normalized) + after);
  11415. }
  11416. for (; container && container != root; container = container.parentNode) {
  11417. point.push(self.dom.nodeIndex(container, normalized));
  11418. }
  11419. return point;
  11420. }
  11421. bookmark.start = getPoint(rng, true);
  11422. if (!self.isCollapsed()) {
  11423. bookmark.end = getPoint(rng);
  11424. }
  11425. return bookmark;
  11426. }
  11427. if (type == 2) {
  11428. element = self.getNode();
  11429. name = element ? element.nodeName : null;
  11430. if (name == 'IMG') {
  11431. return {name: name, index: findIndex(name, element)};
  11432. }
  11433. if (self.tridentSel) {
  11434. return self.tridentSel.getBookmark(type);
  11435. }
  11436. return getLocation();
  11437. }
  11438. // Handle simple range
  11439. if (type) {
  11440. return {rng: self.getRng()};
  11441. }
  11442. rng = self.getRng();
  11443. id = dom.uniqueId();
  11444. collapsed = self.isCollapsed();
  11445. styles = 'overflow:hidden;line-height:0px';
  11446. // Explorer method
  11447. if (rng.duplicate || rng.item) {
  11448. // Text selection
  11449. if (!rng.item) {
  11450. rng2 = rng.duplicate();
  11451. try {
  11452. // Insert start marker
  11453. rng.collapse();
  11454. rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
  11455. // Insert end marker
  11456. if (!collapsed) {
  11457. rng2.collapse(false);
  11458. // Detect the empty space after block elements in IE and move the
  11459. // end back one character <p></p>] becomes <p>]</p>
  11460. rng.moveToElementText(rng2.parentElement());
  11461. if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
  11462. rng2.move('character', -1);
  11463. }
  11464. rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
  11465. }
  11466. } catch (ex) {
  11467. // IE might throw unspecified error so lets ignore it
  11468. return null;
  11469. }
  11470. } else {
  11471. // Control selection
  11472. element = rng.item(0);
  11473. name = element.nodeName;
  11474. return {name: name, index: findIndex(name, element)};
  11475. }
  11476. } else {
  11477. element = self.getNode();
  11478. name = element.nodeName;
  11479. if (name == 'IMG') {
  11480. return {name: name, index: findIndex(name, element)};
  11481. }
  11482. // W3C method
  11483. rng2 = normalizeTableCellSelection(rng.cloneRange());
  11484. // Insert end marker
  11485. if (!collapsed) {
  11486. rng2.collapse(false);
  11487. rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
  11488. }
  11489. rng = normalizeTableCellSelection(rng);
  11490. rng.collapse(true);
  11491. rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
  11492. }
  11493. self.moveToBookmark({id: id, keep: 1});
  11494. return {id: id};
  11495. },
  11496. /**
  11497. * Restores the selection to the specified bookmark.
  11498. *
  11499. * @method moveToBookmark
  11500. * @param {Object} bookmark Bookmark to restore selection from.
  11501. * @return {Boolean} true/false if it was successful or not.
  11502. * @example
  11503. * // Stores a bookmark of the current selection
  11504. * var bm = tinymce.activeEditor.selection.getBookmark();
  11505. *
  11506. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  11507. *
  11508. * // Restore the selection bookmark
  11509. * tinymce.activeEditor.selection.moveToBookmark(bm);
  11510. */
  11511. moveToBookmark: function(bookmark) {
  11512. var self = this, dom = self.dom, rng, root, startContainer, endContainer, startOffset, endOffset;
  11513. function setEndPoint(start) {
  11514. var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
  11515. if (point) {
  11516. offset = point[0];
  11517. // Find container node
  11518. for (node = root, i = point.length - 1; i >= 1; i--) {
  11519. children = node.childNodes;
  11520. if (point[i] > children.length - 1) {
  11521. return;
  11522. }
  11523. node = children[point[i]];
  11524. }
  11525. // Move text offset to best suitable location
  11526. if (node.nodeType === 3) {
  11527. offset = Math.min(point[0], node.nodeValue.length);
  11528. }
  11529. // Move element offset to best suitable location
  11530. if (node.nodeType === 1) {
  11531. offset = Math.min(point[0], node.childNodes.length);
  11532. }
  11533. // Set offset within container node
  11534. if (start) {
  11535. rng.setStart(node, offset);
  11536. } else {
  11537. rng.setEnd(node, offset);
  11538. }
  11539. }
  11540. return true;
  11541. }
  11542. function restoreEndPoint(suffix) {
  11543. var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
  11544. if (marker) {
  11545. node = marker.parentNode;
  11546. if (suffix == 'start') {
  11547. if (!keep) {
  11548. idx = dom.nodeIndex(marker);
  11549. } else {
  11550. node = marker.firstChild;
  11551. idx = 1;
  11552. }
  11553. startContainer = endContainer = node;
  11554. startOffset = endOffset = idx;
  11555. } else {
  11556. if (!keep) {
  11557. idx = dom.nodeIndex(marker);
  11558. } else {
  11559. node = marker.firstChild;
  11560. idx = 1;
  11561. }
  11562. endContainer = node;
  11563. endOffset = idx;
  11564. }
  11565. if (!keep) {
  11566. prev = marker.previousSibling;
  11567. next = marker.nextSibling;
  11568. // Remove all marker text nodes
  11569. each(grep(marker.childNodes), function(node) {
  11570. if (node.nodeType == 3) {
  11571. node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
  11572. }
  11573. });
  11574. // Remove marker but keep children if for example contents where inserted into the marker
  11575. // Also remove duplicated instances of the marker for example by a
  11576. // split operation or by WebKit auto split on paste feature
  11577. while ((marker = dom.get(bookmark.id + '_' + suffix))) {
  11578. dom.remove(marker, 1);
  11579. }
  11580. // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
  11581. // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
  11582. // isn't worth the effort. Sorry, Opera but it's just a fact
  11583. if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) {
  11584. idx = prev.nodeValue.length;
  11585. prev.appendData(next.nodeValue);
  11586. dom.remove(next);
  11587. if (suffix == 'start') {
  11588. startContainer = endContainer = prev;
  11589. startOffset = endOffset = idx;
  11590. } else {
  11591. endContainer = prev;
  11592. endOffset = idx;
  11593. }
  11594. }
  11595. }
  11596. }
  11597. }
  11598. function addBogus(node) {
  11599. // Adds a bogus BR element for empty block elements
  11600. if (dom.isBlock(node) && !node.innerHTML && !isIE) {
  11601. node.innerHTML = '<br data-mce-bogus="1" />';
  11602. }
  11603. return node;
  11604. }
  11605. if (bookmark) {
  11606. if (bookmark.start) {
  11607. rng = dom.createRng();
  11608. root = dom.getRoot();
  11609. if (self.tridentSel) {
  11610. return self.tridentSel.moveToBookmark(bookmark);
  11611. }
  11612. if (setEndPoint(true) && setEndPoint()) {
  11613. self.setRng(rng);
  11614. }
  11615. } else if (bookmark.id) {
  11616. // Restore start/end points
  11617. restoreEndPoint('start');
  11618. restoreEndPoint('end');
  11619. if (startContainer) {
  11620. rng = dom.createRng();
  11621. rng.setStart(addBogus(startContainer), startOffset);
  11622. rng.setEnd(addBogus(endContainer), endOffset);
  11623. self.setRng(rng);
  11624. }
  11625. } else if (bookmark.name) {
  11626. self.select(dom.select(bookmark.name)[bookmark.index]);
  11627. } else if (bookmark.rng) {
  11628. self.setRng(bookmark.rng);
  11629. }
  11630. }
  11631. },
  11632. /**
  11633. * Selects the specified element. This will place the start and end of the selection range around the element.
  11634. *
  11635. * @method select
  11636. * @param {Element} node HMTL DOM element to select.
  11637. * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
  11638. * @return {Element} Selected element the same element as the one that got passed in.
  11639. * @example
  11640. * // Select the first paragraph in the active editor
  11641. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  11642. */
  11643. select: function(node, content) {
  11644. var self = this, dom = self.dom, rng = dom.createRng(), idx;
  11645. // Clear stored range set by FocusManager
  11646. self.lastFocusBookmark = null;
  11647. if (node) {
  11648. if (!content && self.controlSelection.controlSelect(node)) {
  11649. return;
  11650. }
  11651. idx = dom.nodeIndex(node);
  11652. rng.setStart(node.parentNode, idx);
  11653. rng.setEnd(node.parentNode, idx + 1);
  11654. // Find first/last text node or BR element
  11655. if (content) {
  11656. self._moveEndPoint(rng, node, true);
  11657. self._moveEndPoint(rng, node);
  11658. }
  11659. self.setRng(rng);
  11660. }
  11661. return node;
  11662. },
  11663. /**
  11664. * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
  11665. *
  11666. * @method isCollapsed
  11667. * @return {Boolean} true/false state if the selection range is collapsed or not.
  11668. * Collapsed means if it's a caret or a larger selection.
  11669. */
  11670. isCollapsed: function() {
  11671. var self = this, rng = self.getRng(), sel = self.getSel();
  11672. if (!rng || rng.item) {
  11673. return false;
  11674. }
  11675. if (rng.compareEndPoints) {
  11676. return rng.compareEndPoints('StartToEnd', rng) === 0;
  11677. }
  11678. return !sel || rng.collapsed;
  11679. },
  11680. /**
  11681. * Collapse the selection to start or end of range.
  11682. *
  11683. * @method collapse
  11684. * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start.
  11685. */
  11686. collapse: function(to_start) {
  11687. var self = this, rng = self.getRng(), node;
  11688. // Control range on IE
  11689. if (rng.item) {
  11690. node = rng.item(0);
  11691. rng = self.win.document.body.createTextRange();
  11692. rng.moveToElementText(node);
  11693. }
  11694. rng.collapse(!!to_start);
  11695. self.setRng(rng);
  11696. },
  11697. /**
  11698. * Returns the browsers internal selection object.
  11699. *
  11700. * @method getSel
  11701. * @return {Selection} Internal browser selection object.
  11702. */
  11703. getSel: function() {
  11704. var win = this.win;
  11705. return win.getSelection ? win.getSelection() : win.document.selection;
  11706. },
  11707. /**
  11708. * Returns the browsers internal range object.
  11709. *
  11710. * @method getRng
  11711. * @param {Boolean} w3c Forces a compatible W3C range on IE.
  11712. * @return {Range} Internal browser range object.
  11713. * @see http://www.quirksmode.org/dom/range_intro.html
  11714. * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
  11715. */
  11716. getRng: function(w3c) {
  11717. var self = this, selection, rng, elm, doc = self.win.document, ieRng;
  11718. function tryCompareBounderyPoints(how, sourceRange, destinationRange) {
  11719. try {
  11720. return sourceRange.compareBoundaryPoints(how, destinationRange);
  11721. } catch (ex) {
  11722. // Gecko throws wrong document exception if the range points
  11723. // to nodes that where removed from the dom #6690
  11724. // Browsers should mutate existing DOMRange instances so that they always point
  11725. // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
  11726. // For performance reasons just return -1
  11727. return -1;
  11728. }
  11729. }
  11730. // Use last rng passed from FocusManager if it's available this enables
  11731. // calls to editor.selection.getStart() to work when caret focus is lost on IE
  11732. if (!w3c && self.lastFocusBookmark) {
  11733. var bookmark = self.lastFocusBookmark;
  11734. // Convert bookmark to range IE 11 fix
  11735. if (bookmark.startContainer) {
  11736. rng = doc.createRange();
  11737. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  11738. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  11739. } else {
  11740. rng = bookmark;
  11741. }
  11742. return rng;
  11743. }
  11744. // Found tridentSel object then we need to use that one
  11745. if (w3c && self.tridentSel) {
  11746. return self.tridentSel.getRangeAt(0);
  11747. }
  11748. try {
  11749. if ((selection = self.getSel())) {
  11750. if (selection.rangeCount > 0) {
  11751. rng = selection.getRangeAt(0);
  11752. } else {
  11753. rng = selection.createRange ? selection.createRange() : doc.createRange();
  11754. }
  11755. }
  11756. } catch (ex) {
  11757. // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
  11758. }
  11759. // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
  11760. // IE 11 doesn't support the selection object so we check for that as well
  11761. if (isIE && rng && rng.setStart && doc.selection) {
  11762. try {
  11763. // IE will sometimes throw an exception here
  11764. ieRng = doc.selection.createRange();
  11765. } catch (ex) {
  11766. }
  11767. if (ieRng && ieRng.item) {
  11768. elm = ieRng.item(0);
  11769. rng = doc.createRange();
  11770. rng.setStartBefore(elm);
  11771. rng.setEndAfter(elm);
  11772. }
  11773. }
  11774. // No range found then create an empty one
  11775. // This can occur when the editor is placed in a hidden container element on Gecko
  11776. // Or on IE when there was an exception
  11777. if (!rng) {
  11778. rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
  11779. }
  11780. // If range is at start of document then move it to start of body
  11781. if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
  11782. elm = self.dom.getRoot();
  11783. rng.setStart(elm, 0);
  11784. rng.setEnd(elm, 0);
  11785. }
  11786. if (self.selectedRange && self.explicitRange) {
  11787. if (tryCompareBounderyPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
  11788. tryCompareBounderyPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
  11789. // Safari, Opera and Chrome only ever select text which causes the range to change.
  11790. // This lets us use the originally set range if the selection hasn't been changed by the user.
  11791. rng = self.explicitRange;
  11792. } else {
  11793. self.selectedRange = null;
  11794. self.explicitRange = null;
  11795. }
  11796. }
  11797. return rng;
  11798. },
  11799. /**
  11800. * Changes the selection to the specified DOM range.
  11801. *
  11802. * @method setRng
  11803. * @param {Range} rng Range to select.
  11804. */
  11805. setRng: function(rng, forward) {
  11806. var self = this, sel;
  11807. // Is IE specific range
  11808. if (rng.select) {
  11809. try {
  11810. rng.select();
  11811. } catch (ex) {
  11812. // Needed for some odd IE bug #1843306
  11813. }
  11814. return;
  11815. }
  11816. if (!self.tridentSel) {
  11817. sel = self.getSel();
  11818. if (sel) {
  11819. self.explicitRange = rng;
  11820. try {
  11821. sel.removeAllRanges();
  11822. sel.addRange(rng);
  11823. } catch (ex) {
  11824. // IE might throw errors here if the editor is within a hidden container and selection is changed
  11825. }
  11826. // Forward is set to false and we have an extend function
  11827. if (forward === false && sel.extend) {
  11828. sel.collapse(rng.endContainer, rng.endOffset);
  11829. sel.extend(rng.startContainer, rng.startOffset);
  11830. }
  11831. // adding range isn't always successful so we need to check range count otherwise an exception can occur
  11832. self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
  11833. }
  11834. } else {
  11835. // Is W3C Range fake range on IE
  11836. if (rng.cloneRange) {
  11837. try {
  11838. self.tridentSel.addRange(rng);
  11839. return;
  11840. } catch (ex) {
  11841. //IE9 throws an error here if called before selection is placed in the editor
  11842. }
  11843. }
  11844. }
  11845. },
  11846. /**
  11847. * Sets the current selection to the specified DOM element.
  11848. *
  11849. * @method setNode
  11850. * @param {Element} elm Element to set as the contents of the selection.
  11851. * @return {Element} Returns the element that got passed in.
  11852. * @example
  11853. * // Inserts a DOM node at current selection/caret location
  11854. * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
  11855. */
  11856. setNode: function(elm) {
  11857. var self = this;
  11858. self.setContent(self.dom.getOuterHTML(elm));
  11859. return elm;
  11860. },
  11861. /**
  11862. * Returns the currently selected element or the common ancestor element for both start and end of the selection.
  11863. *
  11864. * @method getNode
  11865. * @return {Element} Currently selected element or common ancestor element.
  11866. * @example
  11867. * // Alerts the currently selected elements node name
  11868. * alert(tinymce.activeEditor.selection.getNode().nodeName);
  11869. */
  11870. getNode: function() {
  11871. var self = this, rng = self.getRng(), elm;
  11872. var startContainer = rng.startContainer, endContainer = rng.endContainer;
  11873. var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();
  11874. function skipEmptyTextNodes(node, forwards) {
  11875. var orig = node;
  11876. while (node && node.nodeType === 3 && node.length === 0) {
  11877. node = forwards ? node.nextSibling : node.previousSibling;
  11878. }
  11879. return node || orig;
  11880. }
  11881. // Range maybe lost after the editor is made visible again
  11882. if (!rng) {
  11883. return root;
  11884. }
  11885. if (rng.setStart) {
  11886. elm = rng.commonAncestorContainer;
  11887. // Handle selection a image or other control like element such as anchors
  11888. if (!rng.collapsed) {
  11889. if (startContainer == endContainer) {
  11890. if (endOffset - startOffset < 2) {
  11891. if (startContainer.hasChildNodes()) {
  11892. elm = startContainer.childNodes[startOffset];
  11893. }
  11894. }
  11895. }
  11896. // If the anchor node is a element instead of a text node then return this element
  11897. //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
  11898. // return sel.anchorNode.childNodes[sel.anchorOffset];
  11899. // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
  11900. // This happens when you double click an underlined word in FireFox.
  11901. if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
  11902. if (startContainer.length === startOffset) {
  11903. startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
  11904. } else {
  11905. startContainer = startContainer.parentNode;
  11906. }
  11907. if (endOffset === 0) {
  11908. endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
  11909. } else {
  11910. endContainer = endContainer.parentNode;
  11911. }
  11912. if (startContainer && startContainer === endContainer) {
  11913. return startContainer;
  11914. }
  11915. }
  11916. }
  11917. if (elm && elm.nodeType == 3) {
  11918. return elm.parentNode;
  11919. }
  11920. return elm;
  11921. }
  11922. elm = rng.item ? rng.item(0) : rng.parentElement();
  11923. // IE 7 might return elements outside the iframe
  11924. if (elm.ownerDocument !== self.win.document) {
  11925. elm = root;
  11926. }
  11927. return elm;
  11928. },
  11929. getSelectedBlocks: function(startElm, endElm) {
  11930. var self = this, dom = self.dom, node, root, selectedBlocks = [];
  11931. root = dom.getRoot();
  11932. startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
  11933. endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
  11934. if (startElm && startElm != root) {
  11935. selectedBlocks.push(startElm);
  11936. }
  11937. if (startElm && endElm && startElm != endElm) {
  11938. node = startElm;
  11939. var walker = new TreeWalker(startElm, root);
  11940. while ((node = walker.next()) && node != endElm) {
  11941. if (dom.isBlock(node)) {
  11942. selectedBlocks.push(node);
  11943. }
  11944. }
  11945. }
  11946. if (endElm && startElm != endElm && endElm != root) {
  11947. selectedBlocks.push(endElm);
  11948. }
  11949. return selectedBlocks;
  11950. },
  11951. isForward: function() {
  11952. var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
  11953. // No support for selection direction then always return true
  11954. if (!sel || !sel.anchorNode || !sel.focusNode) {
  11955. return true;
  11956. }
  11957. anchorRange = dom.createRng();
  11958. anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
  11959. anchorRange.collapse(true);
  11960. focusRange = dom.createRng();
  11961. focusRange.setStart(sel.focusNode, sel.focusOffset);
  11962. focusRange.collapse(true);
  11963. return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
  11964. },
  11965. normalize: function() {
  11966. var self = this, rng = self.getRng();
  11967. if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
  11968. self.setRng(rng, self.isForward());
  11969. }
  11970. return rng;
  11971. },
  11972. /**
  11973. * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
  11974. *
  11975. * @method selectorChanged
  11976. * @param {String} selector CSS selector to check for.
  11977. * @param {function} callback Callback with state and args when the selector is matches or not.
  11978. */
  11979. selectorChanged: function(selector, callback) {
  11980. var self = this, currentSelectors;
  11981. if (!self.selectorChangedData) {
  11982. self.selectorChangedData = {};
  11983. currentSelectors = {};
  11984. self.editor.on('NodeChange', function(e) {
  11985. var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
  11986. // Check for new matching selectors
  11987. each(self.selectorChangedData, function(callbacks, selector) {
  11988. each(parents, function(node) {
  11989. if (dom.is(node, selector)) {
  11990. if (!currentSelectors[selector]) {
  11991. // Execute callbacks
  11992. each(callbacks, function(callback) {
  11993. callback(true, {node: node, selector: selector, parents: parents});
  11994. });
  11995. currentSelectors[selector] = callbacks;
  11996. }
  11997. matchedSelectors[selector] = callbacks;
  11998. return false;
  11999. }
  12000. });
  12001. });
  12002. // Check if current selectors still match
  12003. each(currentSelectors, function(callbacks, selector) {
  12004. if (!matchedSelectors[selector]) {
  12005. delete currentSelectors[selector];
  12006. each(callbacks, function(callback) {
  12007. callback(false, {node: node, selector: selector, parents: parents});
  12008. });
  12009. }
  12010. });
  12011. });
  12012. }
  12013. // Add selector listeners
  12014. if (!self.selectorChangedData[selector]) {
  12015. self.selectorChangedData[selector] = [];
  12016. }
  12017. self.selectorChangedData[selector].push(callback);
  12018. return self;
  12019. },
  12020. getScrollContainer: function() {
  12021. var scrollContainer, node = this.dom.getRoot();
  12022. while (node && node.nodeName != 'BODY') {
  12023. if (node.scrollHeight > node.clientHeight) {
  12024. scrollContainer = node;
  12025. break;
  12026. }
  12027. node = node.parentNode;
  12028. }
  12029. return scrollContainer;
  12030. },
  12031. scrollIntoView: function(elm) {
  12032. var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH;
  12033. function getPos(elm) {
  12034. var x = 0, y = 0;
  12035. var offsetParent = elm;
  12036. while (offsetParent && offsetParent.nodeType) {
  12037. x += offsetParent.offsetLeft || 0;
  12038. y += offsetParent.offsetTop || 0;
  12039. offsetParent = offsetParent.offsetParent;
  12040. }
  12041. return {x: x, y: y};
  12042. }
  12043. if (root.nodeName != 'BODY') {
  12044. var scrollContainer = self.getScrollContainer();
  12045. if (scrollContainer) {
  12046. y = getPos(elm).y - getPos(scrollContainer).y;
  12047. viewPortH = scrollContainer.clientHeight;
  12048. viewPortY = scrollContainer.scrollTop;
  12049. if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
  12050. scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
  12051. }
  12052. return;
  12053. }
  12054. }
  12055. viewPort = dom.getViewPort(self.editor.getWin());
  12056. y = dom.getPos(elm).y;
  12057. viewPortY = viewPort.y;
  12058. viewPortH = viewPort.h;
  12059. if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
  12060. self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
  12061. }
  12062. },
  12063. _moveEndPoint: function(rng, node, start) {
  12064. var root = node, walker = new TreeWalker(node, root);
  12065. var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
  12066. do {
  12067. // Text node
  12068. if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
  12069. if (start) {
  12070. rng.setStart(node, 0);
  12071. } else {
  12072. rng.setEnd(node, node.nodeValue.length);
  12073. }
  12074. return;
  12075. }
  12076. // BR/IMG/INPUT elements
  12077. if (nonEmptyElementsMap[node.nodeName]) {
  12078. if (start) {
  12079. rng.setStartBefore(node);
  12080. } else {
  12081. if (node.nodeName == 'BR') {
  12082. rng.setEndBefore(node);
  12083. } else {
  12084. rng.setEndAfter(node);
  12085. }
  12086. }
  12087. return;
  12088. }
  12089. // Found empty text block old IE can place the selection inside those
  12090. if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
  12091. if (start) {
  12092. rng.setStart(node, 0);
  12093. } else {
  12094. rng.setEnd(node, 0);
  12095. }
  12096. return;
  12097. }
  12098. } while ((node = (start ? walker.next() : walker.prev())));
  12099. // Failed to find any text node or other suitable location then move to the root of body
  12100. if (root.nodeName == 'BODY') {
  12101. if (start) {
  12102. rng.setStart(root, 0);
  12103. } else {
  12104. rng.setEnd(root, root.childNodes.length);
  12105. }
  12106. }
  12107. },
  12108. destroy: function() {
  12109. this.win = null;
  12110. this.controlSelection.destroy();
  12111. }
  12112. };
  12113. return Selection;
  12114. });
  12115. // Included from: js/tinymce/classes/fmt/Preview.js
  12116. /**
  12117. * Preview.js
  12118. *
  12119. * Copyright, Moxiecode Systems AB
  12120. * Released under LGPL License.
  12121. *
  12122. * License: http://www.tinymce.com/license
  12123. * Contributing: http://www.tinymce.com/contributing
  12124. */
  12125. /**
  12126. * Internal class for generating previews styles for formats.
  12127. *
  12128. * Example:
  12129. * Preview.getCssText(editor, 'bold');
  12130. *
  12131. * @class tinymce.fmt.Preview
  12132. * @private
  12133. */
  12134. define("tinymce/fmt/Preview", [
  12135. "tinymce/util/Tools"
  12136. ], function(Tools) {
  12137. var each = Tools.each;
  12138. function getCssText(editor, format) {
  12139. var name, previewElm, dom = editor.dom;
  12140. var previewCss = '', parentFontSize, previewStyles;
  12141. previewStyles = editor.settings.preview_styles;
  12142. // No preview forced
  12143. if (previewStyles === false) {
  12144. return '';
  12145. }
  12146. // Default preview
  12147. if (!previewStyles) {
  12148. previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
  12149. 'text-transform color background-color border border-radius outline text-shadow';
  12150. }
  12151. // Removes any variables since these can't be previewed
  12152. function removeVars(val) {
  12153. return val.replace(/%(\w+)/g, '');
  12154. }
  12155. // Create block/inline element to use for preview
  12156. if (typeof(format) == "string") {
  12157. format = editor.formatter.get(format);
  12158. if (!format) {
  12159. return;
  12160. }
  12161. format = format[0];
  12162. }
  12163. name = format.block || format.inline || 'span';
  12164. previewElm = dom.create(name);
  12165. // Add format styles to preview element
  12166. each(format.styles, function(value, name) {
  12167. value = removeVars(value);
  12168. if (value) {
  12169. dom.setStyle(previewElm, name, value);
  12170. }
  12171. });
  12172. // Add attributes to preview element
  12173. each(format.attributes, function(value, name) {
  12174. value = removeVars(value);
  12175. if (value) {
  12176. dom.setAttrib(previewElm, name, value);
  12177. }
  12178. });
  12179. // Add classes to preview element
  12180. each(format.classes, function(value) {
  12181. value = removeVars(value);
  12182. if (!dom.hasClass(previewElm, value)) {
  12183. dom.addClass(previewElm, value);
  12184. }
  12185. });
  12186. editor.fire('PreviewFormats');
  12187. // Add the previewElm outside the visual area
  12188. dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
  12189. editor.getBody().appendChild(previewElm);
  12190. // Get parent container font size so we can compute px values out of em/% for older IE:s
  12191. parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
  12192. parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
  12193. each(previewStyles.split(' '), function(name) {
  12194. var value = dom.getStyle(previewElm, name, true);
  12195. // If background is transparent then check if the body has a background color we can use
  12196. if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
  12197. value = dom.getStyle(editor.getBody(), name, true);
  12198. // Ignore white since it's the default color, not the nicest fix
  12199. // TODO: Fix this by detecting runtime style
  12200. if (dom.toHex(value).toLowerCase() == '#ffffff') {
  12201. return;
  12202. }
  12203. }
  12204. if (name == 'color') {
  12205. // Ignore black since it's the default color, not the nicest fix
  12206. // TODO: Fix this by detecting runtime style
  12207. if (dom.toHex(value).toLowerCase() == '#000000') {
  12208. return;
  12209. }
  12210. }
  12211. // Old IE won't calculate the font size so we need to do that manually
  12212. if (name == 'font-size') {
  12213. if (/em|%$/.test(value)) {
  12214. if (parentFontSize === 0) {
  12215. return;
  12216. }
  12217. // Convert font size from em/% to px
  12218. value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
  12219. value = (value * parentFontSize) + 'px';
  12220. }
  12221. }
  12222. if (name == "border" && value) {
  12223. previewCss += 'padding:0 2px;';
  12224. }
  12225. previewCss += name + ':' + value + ';';
  12226. });
  12227. editor.fire('AfterPreviewFormats');
  12228. //previewCss += 'line-height:normal';
  12229. dom.remove(previewElm);
  12230. return previewCss;
  12231. }
  12232. return {
  12233. getCssText: getCssText
  12234. };
  12235. });
  12236. // Included from: js/tinymce/classes/Formatter.js
  12237. /**
  12238. * Formatter.js
  12239. *
  12240. * Copyright, Moxiecode Systems AB
  12241. * Released under LGPL License.
  12242. *
  12243. * License: http://www.tinymce.com/license
  12244. * Contributing: http://www.tinymce.com/contributing
  12245. */
  12246. /**
  12247. * Text formatter engine class. This class is used to apply formats like bold, italic, font size
  12248. * etc to the current selection or specific nodes. This engine was build to replace the browsers
  12249. * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
  12250. *
  12251. * @class tinymce.Formatter
  12252. * @example
  12253. * tinymce.activeEditor.formatter.register('mycustomformat', {
  12254. * inline: 'span',
  12255. * styles: {color: '#ff0000'}
  12256. * });
  12257. *
  12258. * tinymce.activeEditor.formatter.apply('mycustomformat');
  12259. */
  12260. define("tinymce/Formatter", [
  12261. "tinymce/dom/TreeWalker",
  12262. "tinymce/dom/RangeUtils",
  12263. "tinymce/util/Tools",
  12264. "tinymce/fmt/Preview"
  12265. ], function(TreeWalker, RangeUtils, Tools, Preview) {
  12266. /**
  12267. * Constructs a new formatter instance.
  12268. *
  12269. * @constructor Formatter
  12270. * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
  12271. */
  12272. return function(ed) {
  12273. var formats = {},
  12274. dom = ed.dom,
  12275. selection = ed.selection,
  12276. rangeUtils = new RangeUtils(dom),
  12277. isValid = ed.schema.isValidChild,
  12278. isBlock = dom.isBlock,
  12279. forcedRootBlock = ed.settings.forced_root_block,
  12280. nodeIndex = dom.nodeIndex,
  12281. INVISIBLE_CHAR = '\uFEFF',
  12282. MCE_ATTR_RE = /^(src|href|style)$/,
  12283. FALSE = false,
  12284. TRUE = true,
  12285. formatChangeData,
  12286. undef,
  12287. getContentEditable = dom.getContentEditable,
  12288. disableCaretContainer,
  12289. markCaretContainersBogus;
  12290. var each = Tools.each,
  12291. grep = Tools.grep,
  12292. walk = Tools.walk,
  12293. extend = Tools.extend;
  12294. function isTextBlock(name) {
  12295. if (name.nodeType) {
  12296. name = name.nodeName;
  12297. }
  12298. return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
  12299. }
  12300. function getParents(node, selector) {
  12301. return dom.getParents(node, selector, dom.getRoot());
  12302. }
  12303. function isCaretNode(node) {
  12304. return node.nodeType === 1 && node.id === '_mce_caret';
  12305. }
  12306. function defaultFormats() {
  12307. register({
  12308. valigntop: [
  12309. {selector: 'td,th', styles: {'verticalAlign': 'top'}}
  12310. ],
  12311. valignmiddle: [
  12312. {selector: 'td,th', styles: {'verticalAlign': 'middle'}}
  12313. ],
  12314. valignbottom: [
  12315. {selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
  12316. ],
  12317. alignleft: [
  12318. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
  12319. {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
  12320. ],
  12321. aligncenter: [
  12322. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
  12323. {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
  12324. {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
  12325. ],
  12326. alignright: [
  12327. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
  12328. {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
  12329. ],
  12330. alignjustify: [
  12331. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
  12332. ],
  12333. bold: [
  12334. {inline: 'strong', remove: 'all'},
  12335. {inline: 'span', styles: {fontWeight: 'bold'}},
  12336. {inline: 'b', remove: 'all'}
  12337. ],
  12338. italic: [
  12339. {inline: 'em', remove: 'all'},
  12340. {inline: 'span', styles: {fontStyle: 'italic'}},
  12341. {inline: 'i', remove: 'all'}
  12342. ],
  12343. underline: [
  12344. {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
  12345. {inline: 'u', remove: 'all'}
  12346. ],
  12347. strikethrough: [
  12348. {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
  12349. {inline: 'strike', remove: 'all'}
  12350. ],
  12351. forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false},
  12352. hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false},
  12353. fontname: {inline: 'span', styles: {fontFamily: '%value'}},
  12354. fontsize: {inline: 'span', styles: {fontSize: '%value'}},
  12355. fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
  12356. blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
  12357. subscript: {inline: 'sub'},
  12358. superscript: {inline: 'sup'},
  12359. code: {inline: 'code'},
  12360. link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
  12361. onmatch: function() {
  12362. return true;
  12363. },
  12364. onformat: function(elm, fmt, vars) {
  12365. each(vars, function(value, key) {
  12366. dom.setAttrib(elm, key, value);
  12367. });
  12368. }
  12369. },
  12370. removeformat: [
  12371. {
  12372. selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
  12373. remove: 'all',
  12374. split: true,
  12375. expand: false,
  12376. block_expand: true,
  12377. deep: true
  12378. },
  12379. {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
  12380. {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
  12381. ]
  12382. });
  12383. // Register default block formats
  12384. each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
  12385. register(name, {block: name, remove: 'all'});
  12386. });
  12387. // Register user defined formats
  12388. register(ed.settings.formats);
  12389. }
  12390. function addKeyboardShortcuts() {
  12391. // Add some inline shortcuts
  12392. ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
  12393. ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
  12394. ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
  12395. // BlockFormat shortcuts keys
  12396. for (var i = 1; i <= 6; i++) {
  12397. ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
  12398. }
  12399. ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
  12400. ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
  12401. ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
  12402. }
  12403. // Public functions
  12404. /**
  12405. * Returns the format by name or all formats if no name is specified.
  12406. *
  12407. * @method get
  12408. * @param {String} name Optional name to retrive by.
  12409. * @return {Array/Object} Array/Object with all registred formats or a specific format.
  12410. */
  12411. function get(name) {
  12412. return name ? formats[name] : formats;
  12413. }
  12414. /**
  12415. * Registers a specific format by name.
  12416. *
  12417. * @method register
  12418. * @param {Object/String} name Name of the format for example "bold".
  12419. * @param {Object/Array} format Optional format object or array of format variants
  12420. * can only be omitted if the first arg is an object.
  12421. */
  12422. function register(name, format) {
  12423. if (name) {
  12424. if (typeof(name) !== 'string') {
  12425. each(name, function(format, name) {
  12426. register(name, format);
  12427. });
  12428. } else {
  12429. // Force format into array and add it to internal collection
  12430. format = format.length ? format : [format];
  12431. each(format, function(format) {
  12432. // Set deep to false by default on selector formats this to avoid removing
  12433. // alignment on images inside paragraphs when alignment is changed on paragraphs
  12434. if (format.deep === undef) {
  12435. format.deep = !format.selector;
  12436. }
  12437. // Default to true
  12438. if (format.split === undef) {
  12439. format.split = !format.selector || format.inline;
  12440. }
  12441. // Default to true
  12442. if (format.remove === undef && format.selector && !format.inline) {
  12443. format.remove = 'none';
  12444. }
  12445. // Mark format as a mixed format inline + block level
  12446. if (format.selector && format.inline) {
  12447. format.mixed = true;
  12448. format.block_expand = true;
  12449. }
  12450. // Split classes if needed
  12451. if (typeof(format.classes) === 'string') {
  12452. format.classes = format.classes.split(/\s+/);
  12453. }
  12454. });
  12455. formats[name] = format;
  12456. }
  12457. }
  12458. }
  12459. function getTextDecoration(node) {
  12460. var decoration;
  12461. ed.dom.getParent(node, function(n) {
  12462. decoration = ed.dom.getStyle(n, 'text-decoration');
  12463. return decoration && decoration !== 'none';
  12464. });
  12465. return decoration;
  12466. }
  12467. function processUnderlineAndColor(node) {
  12468. var textDecoration;
  12469. if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
  12470. textDecoration = getTextDecoration(node.parentNode);
  12471. if (ed.dom.getStyle(node, 'color') && textDecoration) {
  12472. ed.dom.setStyle(node, 'text-decoration', textDecoration);
  12473. } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
  12474. ed.dom.setStyle(node, 'text-decoration', null);
  12475. }
  12476. }
  12477. }
  12478. /**
  12479. * Applies the specified format to the current selection or specified node.
  12480. *
  12481. * @method apply
  12482. * @param {String} name Name of format to apply.
  12483. * @param {Object} vars Optional list of variables to replace within format before applying it.
  12484. * @param {Node} node Optional node to apply the format to defaults to current selection.
  12485. */
  12486. function apply(name, vars, node) {
  12487. var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
  12488. function setElementFormat(elm, fmt) {
  12489. fmt = fmt || format;
  12490. if (elm) {
  12491. if (fmt.onformat) {
  12492. fmt.onformat(elm, fmt, vars, node);
  12493. }
  12494. each(fmt.styles, function(value, name) {
  12495. dom.setStyle(elm, name, replaceVars(value, vars));
  12496. });
  12497. // Needed for the WebKit span spam bug
  12498. // TODO: Remove this once WebKit/Blink fixes this
  12499. if (fmt.styles) {
  12500. var styleVal = dom.getAttrib(elm, 'style');
  12501. if (styleVal) {
  12502. elm.setAttribute('data-mce-style', styleVal);
  12503. }
  12504. }
  12505. each(fmt.attributes, function(value, name) {
  12506. dom.setAttrib(elm, name, replaceVars(value, vars));
  12507. });
  12508. each(fmt.classes, function(value) {
  12509. value = replaceVars(value, vars);
  12510. if (!dom.hasClass(elm, value)) {
  12511. dom.addClass(elm, value);
  12512. }
  12513. });
  12514. }
  12515. }
  12516. function adjustSelectionToVisibleSelection() {
  12517. function findSelectionEnd(start, end) {
  12518. var walker = new TreeWalker(end);
  12519. for (node = walker.current(); node; node = walker.prev()) {
  12520. if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
  12521. return node;
  12522. }
  12523. }
  12524. }
  12525. // Adjust selection so that a end container with a end offset of zero is not included in the selection
  12526. // as this isn't visible to the user.
  12527. var rng = ed.selection.getRng();
  12528. var start = rng.startContainer;
  12529. var end = rng.endContainer;
  12530. if (start != end && rng.endOffset === 0) {
  12531. var newEnd = findSelectionEnd(start, end);
  12532. var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
  12533. rng.setEnd(newEnd, endOffset);
  12534. }
  12535. return rng;
  12536. }
  12537. function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
  12538. var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
  12539. // find the index of the first child list.
  12540. each(node.childNodes, function(n, index) {
  12541. if (n.nodeName === "UL" || n.nodeName === "OL") {
  12542. listIndex = index;
  12543. list = n;
  12544. return false;
  12545. }
  12546. });
  12547. // get the index of the bookmarks
  12548. each(node.childNodes, function(n, index) {
  12549. if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
  12550. if (n.id == bookmark.id + "_start") {
  12551. startIndex = index;
  12552. } else if (n.id == bookmark.id + "_end") {
  12553. endIndex = index;
  12554. }
  12555. }
  12556. });
  12557. // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
  12558. if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
  12559. each(grep(node.childNodes), process);
  12560. return 0;
  12561. } else {
  12562. currentWrapElm = dom.clone(wrapElm, FALSE);
  12563. // create a list of the nodes on the same side of the list as the selection
  12564. each(grep(node.childNodes), function(n, index) {
  12565. if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
  12566. nodes.push(n);
  12567. n.parentNode.removeChild(n);
  12568. }
  12569. });
  12570. // insert the wrapping element either before or after the list.
  12571. if (startIndex < listIndex) {
  12572. node.insertBefore(currentWrapElm, list);
  12573. } else if (startIndex > listIndex) {
  12574. node.insertBefore(currentWrapElm, list.nextSibling);
  12575. }
  12576. // add the new nodes to the list.
  12577. newWrappers.push(currentWrapElm);
  12578. each(nodes, function(node) {
  12579. currentWrapElm.appendChild(node);
  12580. });
  12581. return currentWrapElm;
  12582. }
  12583. }
  12584. function applyRngStyle(rng, bookmark, node_specific) {
  12585. var newWrappers = [], wrapName, wrapElm, contentEditable = true;
  12586. // Setup wrapper element
  12587. wrapName = format.inline || format.block;
  12588. wrapElm = dom.create(wrapName);
  12589. setElementFormat(wrapElm);
  12590. rangeUtils.walk(rng, function(nodes) {
  12591. var currentWrapElm;
  12592. /**
  12593. * Process a list of nodes wrap them.
  12594. */
  12595. function process(node) {
  12596. var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
  12597. lastContentEditable = contentEditable;
  12598. nodeName = node.nodeName.toLowerCase();
  12599. parentName = node.parentNode.nodeName.toLowerCase();
  12600. // Node has a contentEditable value
  12601. if (node.nodeType === 1 && getContentEditable(node)) {
  12602. lastContentEditable = contentEditable;
  12603. contentEditable = getContentEditable(node) === "true";
  12604. hasContentEditableState = true; // We don't want to wrap the container only it's children
  12605. }
  12606. // Stop wrapping on br elements
  12607. if (isEq(nodeName, 'br')) {
  12608. currentWrapElm = 0;
  12609. // Remove any br elements when we wrap things
  12610. if (format.block) {
  12611. dom.remove(node);
  12612. }
  12613. return;
  12614. }
  12615. // If node is wrapper type
  12616. if (format.wrapper && matchNode(node, name, vars)) {
  12617. currentWrapElm = 0;
  12618. return;
  12619. }
  12620. // Can we rename the block
  12621. // TODO: Break this if up, too complex
  12622. if (contentEditable && !hasContentEditableState && format.block &&
  12623. !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
  12624. node = dom.rename(node, wrapName);
  12625. setElementFormat(node);
  12626. newWrappers.push(node);
  12627. currentWrapElm = 0;
  12628. return;
  12629. }
  12630. // Handle selector patterns
  12631. if (format.selector) {
  12632. // Look for matching formats
  12633. each(formatList, function(format) {
  12634. // Check collapsed state if it exists
  12635. if ('collapsed' in format && format.collapsed !== isCollapsed) {
  12636. return;
  12637. }
  12638. if (dom.is(node, format.selector) && !isCaretNode(node)) {
  12639. setElementFormat(node, format);
  12640. found = true;
  12641. }
  12642. });
  12643. // Continue processing if a selector match wasn't found and a inline element is defined
  12644. if (!format.inline || found) {
  12645. currentWrapElm = 0;
  12646. return;
  12647. }
  12648. }
  12649. // Is it valid to wrap this item
  12650. // TODO: Break this if up, too complex
  12651. if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
  12652. !(!node_specific && node.nodeType === 3 &&
  12653. node.nodeValue.length === 1 &&
  12654. node.nodeValue.charCodeAt(0) === 65279) &&
  12655. !isCaretNode(node) &&
  12656. (!format.inline || !isBlock(node))) {
  12657. // Start wrapping
  12658. if (!currentWrapElm) {
  12659. // Wrap the node
  12660. currentWrapElm = dom.clone(wrapElm, FALSE);
  12661. node.parentNode.insertBefore(currentWrapElm, node);
  12662. newWrappers.push(currentWrapElm);
  12663. }
  12664. currentWrapElm.appendChild(node);
  12665. } else if (nodeName == 'li' && bookmark) {
  12666. // Start wrapping - if we are in a list node and have a bookmark, then
  12667. // we will always begin by wrapping in a new element.
  12668. currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
  12669. } else {
  12670. // Start a new wrapper for possible children
  12671. currentWrapElm = 0;
  12672. each(grep(node.childNodes), process);
  12673. if (hasContentEditableState) {
  12674. contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  12675. }
  12676. // End the last wrapper
  12677. currentWrapElm = 0;
  12678. }
  12679. }
  12680. // Process siblings from range
  12681. each(nodes, process);
  12682. });
  12683. // Wrap links inside as well, for example color inside a link when the wrapper is around the link
  12684. if (format.wrap_links === false) {
  12685. each(newWrappers, function(node) {
  12686. function process(node) {
  12687. var i, currentWrapElm, children;
  12688. if (node.nodeName === 'A') {
  12689. currentWrapElm = dom.clone(wrapElm, FALSE);
  12690. newWrappers.push(currentWrapElm);
  12691. children = grep(node.childNodes);
  12692. for (i = 0; i < children.length; i++) {
  12693. currentWrapElm.appendChild(children[i]);
  12694. }
  12695. node.appendChild(currentWrapElm);
  12696. }
  12697. each(grep(node.childNodes), process);
  12698. }
  12699. process(node);
  12700. });
  12701. }
  12702. // Cleanup
  12703. each(newWrappers, function(node) {
  12704. var childCount;
  12705. function getChildCount(node) {
  12706. var count = 0;
  12707. each(node.childNodes, function(node) {
  12708. if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
  12709. count++;
  12710. }
  12711. });
  12712. return count;
  12713. }
  12714. function mergeStyles(node) {
  12715. var child, clone;
  12716. each(node.childNodes, function(node) {
  12717. if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
  12718. child = node;
  12719. return FALSE; // break loop
  12720. }
  12721. });
  12722. // If child was found and of the same type as the current node
  12723. if (child && !isBookmarkNode(child) && matchName(child, format)) {
  12724. clone = dom.clone(child, FALSE);
  12725. setElementFormat(clone);
  12726. dom.replace(clone, node, TRUE);
  12727. dom.remove(child, 1);
  12728. }
  12729. return clone || node;
  12730. }
  12731. childCount = getChildCount(node);
  12732. // Remove empty nodes but only if there is multiple wrappers and they are not block
  12733. // elements so never remove single <h1></h1> since that would remove the
  12734. // currrent empty block element where the caret is at
  12735. if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
  12736. dom.remove(node, 1);
  12737. return;
  12738. }
  12739. if (format.inline || format.wrapper) {
  12740. // Merges the current node with it's children of similar type to reduce the number of elements
  12741. if (!format.exact && childCount === 1) {
  12742. node = mergeStyles(node);
  12743. }
  12744. // Remove/merge children
  12745. each(formatList, function(format) {
  12746. // Merge all children of similar type will move styles from child to parent
  12747. // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
  12748. // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
  12749. each(dom.select(format.inline, node), function(child) {
  12750. var parent;
  12751. if (isBookmarkNode(child)) {
  12752. return;
  12753. }
  12754. // When wrap_links is set to false we don't want
  12755. // to remove the format on children within links
  12756. if (format.wrap_links === false) {
  12757. parent = child.parentNode;
  12758. do {
  12759. if (parent.nodeName === 'A') {
  12760. return;
  12761. }
  12762. } while ((parent = parent.parentNode));
  12763. }
  12764. removeFormat(format, vars, child, format.exact ? child : null);
  12765. });
  12766. });
  12767. // Remove child if direct parent is of same type
  12768. if (matchNode(node.parentNode, name, vars)) {
  12769. dom.remove(node, 1);
  12770. node = 0;
  12771. return TRUE;
  12772. }
  12773. // Look for parent with similar style format
  12774. if (format.merge_with_parents) {
  12775. dom.getParent(node.parentNode, function(parent) {
  12776. if (matchNode(parent, name, vars)) {
  12777. dom.remove(node, 1);
  12778. node = 0;
  12779. return TRUE;
  12780. }
  12781. });
  12782. }
  12783. // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
  12784. if (node && format.merge_siblings !== false) {
  12785. node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
  12786. node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
  12787. }
  12788. }
  12789. });
  12790. }
  12791. if (format) {
  12792. if (node) {
  12793. if (node.nodeType) {
  12794. rng = dom.createRng();
  12795. rng.setStartBefore(node);
  12796. rng.setEndAfter(node);
  12797. applyRngStyle(expandRng(rng, formatList), null, true);
  12798. } else {
  12799. applyRngStyle(node, null, true);
  12800. }
  12801. } else {
  12802. if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
  12803. // Obtain selection node before selection is unselected by applyRngStyle()
  12804. var curSelNode = ed.selection.getNode();
  12805. // If the formats have a default block and we can't find a parent block then
  12806. // start wrapping it with a DIV this is for forced_root_blocks: false
  12807. // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
  12808. if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
  12809. apply(formatList[0].defaultBlock);
  12810. }
  12811. // Apply formatting to selection
  12812. ed.selection.setRng(adjustSelectionToVisibleSelection());
  12813. bookmark = selection.getBookmark();
  12814. applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
  12815. // Colored nodes should be underlined so that the color of the underline matches the text color.
  12816. if (format.styles && (format.styles.color || format.styles.textDecoration)) {
  12817. walk(curSelNode, processUnderlineAndColor, 'childNodes');
  12818. processUnderlineAndColor(curSelNode);
  12819. }
  12820. selection.moveToBookmark(bookmark);
  12821. moveStart(selection.getRng(TRUE));
  12822. ed.nodeChanged();
  12823. } else {
  12824. performCaretAction('apply', name, vars);
  12825. }
  12826. }
  12827. }
  12828. }
  12829. /**
  12830. * Removes the specified format from the current selection or specified node.
  12831. *
  12832. * @method remove
  12833. * @param {String} name Name of format to remove.
  12834. * @param {Object} vars Optional list of variables to replace within format before removing it.
  12835. * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
  12836. */
  12837. function remove(name, vars, node) {
  12838. var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
  12839. // Merges the styles for each node
  12840. function process(node) {
  12841. var children, i, l, lastContentEditable, hasContentEditableState;
  12842. // Node has a contentEditable value
  12843. if (node.nodeType === 1 && getContentEditable(node)) {
  12844. lastContentEditable = contentEditable;
  12845. contentEditable = getContentEditable(node) === "true";
  12846. hasContentEditableState = true; // We don't want to wrap the container only it's children
  12847. }
  12848. // Grab the children first since the nodelist might be changed
  12849. children = grep(node.childNodes);
  12850. // Process current node
  12851. if (contentEditable && !hasContentEditableState) {
  12852. for (i = 0, l = formatList.length; i < l; i++) {
  12853. if (removeFormat(formatList[i], vars, node, node)) {
  12854. break;
  12855. }
  12856. }
  12857. }
  12858. // Process the children
  12859. if (format.deep) {
  12860. if (children.length) {
  12861. for (i = 0, l = children.length; i < l; i++) {
  12862. process(children[i]);
  12863. }
  12864. if (hasContentEditableState) {
  12865. contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  12866. }
  12867. }
  12868. }
  12869. }
  12870. function findFormatRoot(container) {
  12871. var formatRoot;
  12872. // Find format root
  12873. each(getParents(container.parentNode).reverse(), function(parent) {
  12874. var format;
  12875. // Find format root element
  12876. if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
  12877. // Is the node matching the format we are looking for
  12878. format = matchNode(parent, name, vars);
  12879. if (format && format.split !== false) {
  12880. formatRoot = parent;
  12881. }
  12882. }
  12883. });
  12884. return formatRoot;
  12885. }
  12886. function wrapAndSplit(format_root, container, target, split) {
  12887. var parent, clone, lastClone, firstClone, i, formatRootParent;
  12888. // Format root found then clone formats and split it
  12889. if (format_root) {
  12890. formatRootParent = format_root.parentNode;
  12891. for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
  12892. clone = dom.clone(parent, FALSE);
  12893. for (i = 0; i < formatList.length; i++) {
  12894. if (removeFormat(formatList[i], vars, clone, clone)) {
  12895. clone = 0;
  12896. break;
  12897. }
  12898. }
  12899. // Build wrapper node
  12900. if (clone) {
  12901. if (lastClone) {
  12902. clone.appendChild(lastClone);
  12903. }
  12904. if (!firstClone) {
  12905. firstClone = clone;
  12906. }
  12907. lastClone = clone;
  12908. }
  12909. }
  12910. // Never split block elements if the format is mixed
  12911. if (split && (!format.mixed || !isBlock(format_root))) {
  12912. container = dom.split(format_root, container);
  12913. }
  12914. // Wrap container in cloned formats
  12915. if (lastClone) {
  12916. target.parentNode.insertBefore(lastClone, target);
  12917. firstClone.appendChild(target);
  12918. }
  12919. }
  12920. return container;
  12921. }
  12922. function splitToFormatRoot(container) {
  12923. return wrapAndSplit(findFormatRoot(container), container, container, true);
  12924. }
  12925. function unwrap(start) {
  12926. var node = dom.get(start ? '_start' : '_end'),
  12927. out = node[start ? 'firstChild' : 'lastChild'];
  12928. // If the end is placed within the start the result will be removed
  12929. // So this checks if the out node is a bookmark node if it is it
  12930. // checks for another more suitable node
  12931. if (isBookmarkNode(out)) {
  12932. out = out[start ? 'firstChild' : 'lastChild'];
  12933. }
  12934. dom.remove(node, true);
  12935. return out;
  12936. }
  12937. function removeRngStyle(rng) {
  12938. var startContainer, endContainer;
  12939. var commonAncestorContainer = rng.commonAncestorContainer;
  12940. rng = expandRng(rng, formatList, TRUE);
  12941. if (format.split) {
  12942. startContainer = getContainer(rng, TRUE);
  12943. endContainer = getContainer(rng);
  12944. if (startContainer != endContainer) {
  12945. // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
  12946. // so let's see if we can use the first child instead
  12947. // This will happen if you triple click a table cell and use remove formatting
  12948. if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
  12949. if (startContainer.nodeName == "TR") {
  12950. startContainer = startContainer.firstChild.firstChild || startContainer;
  12951. } else {
  12952. startContainer = startContainer.firstChild || startContainer;
  12953. }
  12954. }
  12955. // Try to adjust endContainer as well if cells on the same row were selected - bug #6410
  12956. if (commonAncestorContainer &&
  12957. /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
  12958. /^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) {
  12959. endContainer = endContainer.firstChild || endContainer;
  12960. }
  12961. // Wrap start/end nodes in span element since these might be cloned/moved
  12962. startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
  12963. endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
  12964. // Split start/end
  12965. splitToFormatRoot(startContainer);
  12966. splitToFormatRoot(endContainer);
  12967. // Unwrap start/end to get real elements again
  12968. startContainer = unwrap(TRUE);
  12969. endContainer = unwrap();
  12970. } else {
  12971. startContainer = endContainer = splitToFormatRoot(startContainer);
  12972. }
  12973. // Update range positions since they might have changed after the split operations
  12974. rng.startContainer = startContainer.parentNode;
  12975. rng.startOffset = nodeIndex(startContainer);
  12976. rng.endContainer = endContainer.parentNode;
  12977. rng.endOffset = nodeIndex(endContainer) + 1;
  12978. }
  12979. // Remove items between start/end
  12980. rangeUtils.walk(rng, function(nodes) {
  12981. each(nodes, function(node) {
  12982. process(node);
  12983. // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
  12984. if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
  12985. node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
  12986. removeFormat({
  12987. 'deep': false,
  12988. 'exact': true,
  12989. 'inline': 'span',
  12990. 'styles': {
  12991. 'textDecoration': 'underline'
  12992. }
  12993. }, null, node);
  12994. }
  12995. });
  12996. });
  12997. }
  12998. // Handle node
  12999. if (node) {
  13000. if (node.nodeType) {
  13001. rng = dom.createRng();
  13002. rng.setStartBefore(node);
  13003. rng.setEndAfter(node);
  13004. removeRngStyle(rng);
  13005. } else {
  13006. removeRngStyle(node);
  13007. }
  13008. return;
  13009. }
  13010. if (!selection.isCollapsed() || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
  13011. bookmark = selection.getBookmark();
  13012. removeRngStyle(selection.getRng(TRUE));
  13013. selection.moveToBookmark(bookmark);
  13014. // Check if start element still has formatting then we are at: "<b>text|</b>text"
  13015. // and need to move the start into the next text node
  13016. if (format.inline && match(name, vars, selection.getStart())) {
  13017. moveStart(selection.getRng(true));
  13018. }
  13019. ed.nodeChanged();
  13020. } else {
  13021. performCaretAction('remove', name, vars);
  13022. }
  13023. }
  13024. /**
  13025. * Toggles the specified format on/off.
  13026. *
  13027. * @method toggle
  13028. * @param {String} name Name of format to apply/remove.
  13029. * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
  13030. * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
  13031. */
  13032. function toggle(name, vars, node) {
  13033. var fmt = get(name);
  13034. if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
  13035. remove(name, vars, node);
  13036. } else {
  13037. apply(name, vars, node);
  13038. }
  13039. }
  13040. /**
  13041. * Return true/false if the specified node has the specified format.
  13042. *
  13043. * @method matchNode
  13044. * @param {Node} node Node to check the format on.
  13045. * @param {String} name Format name to check.
  13046. * @param {Object} vars Optional list of variables to replace before checking it.
  13047. * @param {Boolean} similar Match format that has similar properties.
  13048. * @return {Object} Returns the format object it matches or undefined if it doesn't match.
  13049. */
  13050. function matchNode(node, name, vars, similar) {
  13051. var formatList = get(name), format, i, classes;
  13052. function matchItems(node, format, item_name) {
  13053. var key, value, items = format[item_name], i;
  13054. // Custom match
  13055. if (format.onmatch) {
  13056. return format.onmatch(node, format, item_name);
  13057. }
  13058. // Check all items
  13059. if (items) {
  13060. // Non indexed object
  13061. if (items.length === undef) {
  13062. for (key in items) {
  13063. if (items.hasOwnProperty(key)) {
  13064. if (item_name === 'attributes') {
  13065. value = dom.getAttrib(node, key);
  13066. } else {
  13067. value = getStyle(node, key);
  13068. }
  13069. if (similar && !value && !format.exact) {
  13070. return;
  13071. }
  13072. if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
  13073. return;
  13074. }
  13075. }
  13076. }
  13077. } else {
  13078. // Only one match needed for indexed arrays
  13079. for (i = 0; i < items.length; i++) {
  13080. if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
  13081. return format;
  13082. }
  13083. }
  13084. }
  13085. }
  13086. return format;
  13087. }
  13088. if (formatList && node) {
  13089. // Check each format in list
  13090. for (i = 0; i < formatList.length; i++) {
  13091. format = formatList[i];
  13092. // Name name, attributes, styles and classes
  13093. if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
  13094. // Match classes
  13095. if ((classes = format.classes)) {
  13096. for (i = 0; i < classes.length; i++) {
  13097. if (!dom.hasClass(node, classes[i])) {
  13098. return;
  13099. }
  13100. }
  13101. }
  13102. return format;
  13103. }
  13104. }
  13105. }
  13106. }
  13107. /**
  13108. * Matches the current selection or specified node against the specified format name.
  13109. *
  13110. * @method match
  13111. * @param {String} name Name of format to match.
  13112. * @param {Object} vars Optional list of variables to replace before checking it.
  13113. * @param {Node} node Optional node to check.
  13114. * @return {boolean} true/false if the specified selection/node matches the format.
  13115. */
  13116. function match(name, vars, node) {
  13117. var startNode;
  13118. function matchParents(node) {
  13119. var root = dom.getRoot();
  13120. if (node === root) {
  13121. return false;
  13122. }
  13123. // Find first node with similar format settings
  13124. node = dom.getParent(node, function(node) {
  13125. return node.parentNode === root || !!matchNode(node, name, vars, true);
  13126. });
  13127. // Do an exact check on the similar format element
  13128. return matchNode(node, name, vars);
  13129. }
  13130. // Check specified node
  13131. if (node) {
  13132. return matchParents(node);
  13133. }
  13134. // Check selected node
  13135. node = selection.getNode();
  13136. if (matchParents(node)) {
  13137. return TRUE;
  13138. }
  13139. // Check start node if it's different
  13140. startNode = selection.getStart();
  13141. if (startNode != node) {
  13142. if (matchParents(startNode)) {
  13143. return TRUE;
  13144. }
  13145. }
  13146. return FALSE;
  13147. }
  13148. /**
  13149. * Matches the current selection against the array of formats and returns a new array with matching formats.
  13150. *
  13151. * @method matchAll
  13152. * @param {Array} names Name of format to match.
  13153. * @param {Object} vars Optional list of variables to replace before checking it.
  13154. * @return {Array} Array with matched formats.
  13155. */
  13156. function matchAll(names, vars) {
  13157. var startElement, matchedFormatNames = [], checkedMap = {};
  13158. // Check start of selection for formats
  13159. startElement = selection.getStart();
  13160. dom.getParent(startElement, function(node) {
  13161. var i, name;
  13162. for (i = 0; i < names.length; i++) {
  13163. name = names[i];
  13164. if (!checkedMap[name] && matchNode(node, name, vars)) {
  13165. checkedMap[name] = true;
  13166. matchedFormatNames.push(name);
  13167. }
  13168. }
  13169. }, dom.getRoot());
  13170. return matchedFormatNames;
  13171. }
  13172. /**
  13173. * Returns true/false if the specified format can be applied to the current selection or not. It
  13174. * will currently only check the state for selector formats, it returns true on all other format types.
  13175. *
  13176. * @method canApply
  13177. * @param {String} name Name of format to check.
  13178. * @return {boolean} true/false if the specified format can be applied to the current selection/node.
  13179. */
  13180. function canApply(name) {
  13181. var formatList = get(name), startNode, parents, i, x, selector;
  13182. if (formatList) {
  13183. startNode = selection.getStart();
  13184. parents = getParents(startNode);
  13185. for (x = formatList.length - 1; x >= 0; x--) {
  13186. selector = formatList[x].selector;
  13187. // Format is not selector based then always return TRUE
  13188. // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
  13189. if (!selector || formatList[x].defaultBlock) {
  13190. return TRUE;
  13191. }
  13192. for (i = parents.length - 1; i >= 0; i--) {
  13193. if (dom.is(parents[i], selector)) {
  13194. return TRUE;
  13195. }
  13196. }
  13197. }
  13198. }
  13199. return FALSE;
  13200. }
  13201. /**
  13202. * Executes the specified callback when the current selection matches the formats or not.
  13203. *
  13204. * @method formatChanged
  13205. * @param {String} formats Comma separated list of formats to check for.
  13206. * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
  13207. * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
  13208. */
  13209. function formatChanged(formats, callback, similar) {
  13210. var currentFormats;
  13211. // Setup format node change logic
  13212. if (!formatChangeData) {
  13213. formatChangeData = {};
  13214. currentFormats = {};
  13215. ed.on('NodeChange', function(e) {
  13216. var parents = getParents(e.element), matchedFormats = {};
  13217. // Check for new formats
  13218. each(formatChangeData, function(callbacks, format) {
  13219. each(parents, function(node) {
  13220. if (matchNode(node, format, {}, callbacks.similar)) {
  13221. if (!currentFormats[format]) {
  13222. // Execute callbacks
  13223. each(callbacks, function(callback) {
  13224. callback(true, {node: node, format: format, parents: parents});
  13225. });
  13226. currentFormats[format] = callbacks;
  13227. }
  13228. matchedFormats[format] = callbacks;
  13229. return false;
  13230. }
  13231. });
  13232. });
  13233. // Check if current formats still match
  13234. each(currentFormats, function(callbacks, format) {
  13235. if (!matchedFormats[format]) {
  13236. delete currentFormats[format];
  13237. each(callbacks, function(callback) {
  13238. callback(false, {node: e.element, format: format, parents: parents});
  13239. });
  13240. }
  13241. });
  13242. });
  13243. }
  13244. // Add format listeners
  13245. each(formats.split(','), function(format) {
  13246. if (!formatChangeData[format]) {
  13247. formatChangeData[format] = [];
  13248. formatChangeData[format].similar = similar;
  13249. }
  13250. formatChangeData[format].push(callback);
  13251. });
  13252. return this;
  13253. }
  13254. /**
  13255. * Returns a preview css text for the specified format.
  13256. *
  13257. * @method getCssText
  13258. * @param {String/Object} format Format to generate preview css text for.
  13259. * @return {String} Css text for the specified format.
  13260. * @example
  13261. * var cssText1 = editor.formatter.getCssText('bold');
  13262. * var cssText2 = editor.formatter.getCssText({inline: 'b'});
  13263. */
  13264. function getCssText(format) {
  13265. return Preview.getCssText(ed, format);
  13266. }
  13267. // Expose to public
  13268. extend(this, {
  13269. get: get,
  13270. register: register,
  13271. apply: apply,
  13272. remove: remove,
  13273. toggle: toggle,
  13274. match: match,
  13275. matchAll: matchAll,
  13276. matchNode: matchNode,
  13277. canApply: canApply,
  13278. formatChanged: formatChanged,
  13279. getCssText: getCssText
  13280. });
  13281. // Initialize
  13282. defaultFormats();
  13283. addKeyboardShortcuts();
  13284. ed.on('BeforeGetContent', function() {
  13285. if (markCaretContainersBogus) {
  13286. markCaretContainersBogus();
  13287. }
  13288. });
  13289. ed.on('mouseup keydown', function(e) {
  13290. if (disableCaretContainer) {
  13291. disableCaretContainer(e);
  13292. }
  13293. });
  13294. // Private functions
  13295. /**
  13296. * Checks if the specified nodes name matches the format inline/block or selector.
  13297. *
  13298. * @private
  13299. * @param {Node} node Node to match against the specified format.
  13300. * @param {Object} format Format object o match with.
  13301. * @return {boolean} true/false if the format matches.
  13302. */
  13303. function matchName(node, format) {
  13304. // Check for inline match
  13305. if (isEq(node, format.inline)) {
  13306. return TRUE;
  13307. }
  13308. // Check for block match
  13309. if (isEq(node, format.block)) {
  13310. return TRUE;
  13311. }
  13312. // Check for selector match
  13313. if (format.selector) {
  13314. return node.nodeType == 1 && dom.is(node, format.selector);
  13315. }
  13316. }
  13317. /**
  13318. * Compares two string/nodes regardless of their case.
  13319. *
  13320. * @private
  13321. * @param {String/Node} Node or string to compare.
  13322. * @param {String/Node} Node or string to compare.
  13323. * @return {boolean} True/false if they match.
  13324. */
  13325. function isEq(str1, str2) {
  13326. str1 = str1 || '';
  13327. str2 = str2 || '';
  13328. str1 = '' + (str1.nodeName || str1);
  13329. str2 = '' + (str2.nodeName || str2);
  13330. return str1.toLowerCase() == str2.toLowerCase();
  13331. }
  13332. /**
  13333. * Returns the style by name on the specified node. This method modifies the style
  13334. * contents to make it more easy to match. This will resolve a few browser issues.
  13335. *
  13336. * @private
  13337. * @param {Node} node to get style from.
  13338. * @param {String} name Style name to get.
  13339. * @return {String} Style item value.
  13340. */
  13341. function getStyle(node, name) {
  13342. return normalizeStyleValue(dom.getStyle(node, name), name);
  13343. }
  13344. /**
  13345. * Normalize style value by name. This method modifies the style contents
  13346. * to make it more easy to match. This will resolve a few browser issues.
  13347. *
  13348. * @private
  13349. * @param {Node} node to get style from.
  13350. * @param {String} name Style name to get.
  13351. * @return {String} Style item value.
  13352. */
  13353. function normalizeStyleValue(value, name) {
  13354. // Force the format to hex
  13355. if (name == 'color' || name == 'backgroundColor') {
  13356. value = dom.toHex(value);
  13357. }
  13358. // Opera will return bold as 700
  13359. if (name == 'fontWeight' && value == 700) {
  13360. value = 'bold';
  13361. }
  13362. // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
  13363. if (name == 'fontFamily') {
  13364. value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
  13365. }
  13366. return '' + value;
  13367. }
  13368. /**
  13369. * Replaces variables in the value. The variable format is %var.
  13370. *
  13371. * @private
  13372. * @param {String} value Value to replace variables in.
  13373. * @param {Object} vars Name/value array with variables to replace.
  13374. * @return {String} New value with replaced variables.
  13375. */
  13376. function replaceVars(value, vars) {
  13377. if (typeof(value) != "string") {
  13378. value = value(vars);
  13379. } else if (vars) {
  13380. value = value.replace(/%(\w+)/g, function(str, name) {
  13381. return vars[name] || str;
  13382. });
  13383. }
  13384. return value;
  13385. }
  13386. function isWhiteSpaceNode(node) {
  13387. return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
  13388. }
  13389. function wrap(node, name, attrs) {
  13390. var wrapper = dom.create(name, attrs);
  13391. node.parentNode.insertBefore(wrapper, node);
  13392. wrapper.appendChild(node);
  13393. return wrapper;
  13394. }
  13395. /**
  13396. * Expands the specified range like object to depending on format.
  13397. *
  13398. * For example on block formats it will move the start/end position
  13399. * to the beginning of the current block.
  13400. *
  13401. * @private
  13402. * @param {Object} rng Range like object.
  13403. * @param {Array} formats Array with formats to expand by.
  13404. * @return {Object} Expanded range like object.
  13405. */
  13406. function expandRng(rng, format, remove) {
  13407. var lastIdx, leaf, endPoint,
  13408. startContainer = rng.startContainer,
  13409. startOffset = rng.startOffset,
  13410. endContainer = rng.endContainer,
  13411. endOffset = rng.endOffset;
  13412. // This function walks up the tree if there is no siblings before/after the node
  13413. function findParentContainer(start) {
  13414. var container, parent, sibling, siblingName, root;
  13415. container = parent = start ? startContainer : endContainer;
  13416. siblingName = start ? 'previousSibling' : 'nextSibling';
  13417. root = dom.getRoot();
  13418. function isBogusBr(node) {
  13419. return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
  13420. }
  13421. // If it's a text node and the offset is inside the text
  13422. if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
  13423. if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
  13424. return container;
  13425. }
  13426. }
  13427. /*eslint no-constant-condition:0 */
  13428. while (true) {
  13429. // Stop expanding on block elements
  13430. if (!format[0].block_expand && isBlock(parent)) {
  13431. return parent;
  13432. }
  13433. // Walk left/right
  13434. for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
  13435. if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
  13436. return parent;
  13437. }
  13438. }
  13439. // Check if we can move up are we at root level or body level
  13440. if (parent.parentNode == root) {
  13441. container = parent;
  13442. break;
  13443. }
  13444. parent = parent.parentNode;
  13445. }
  13446. return container;
  13447. }
  13448. // This function walks down the tree to find the leaf at the selection.
  13449. // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
  13450. function findLeaf(node, offset) {
  13451. if (offset === undef) {
  13452. offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  13453. }
  13454. while (node && node.hasChildNodes()) {
  13455. node = node.childNodes[offset];
  13456. if (node) {
  13457. offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  13458. }
  13459. }
  13460. return { node: node, offset: offset };
  13461. }
  13462. // If index based start position then resolve it
  13463. if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  13464. lastIdx = startContainer.childNodes.length - 1;
  13465. startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
  13466. if (startContainer.nodeType == 3) {
  13467. startOffset = 0;
  13468. }
  13469. }
  13470. // If index based end position then resolve it
  13471. if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  13472. lastIdx = endContainer.childNodes.length - 1;
  13473. endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
  13474. if (endContainer.nodeType == 3) {
  13475. endOffset = endContainer.nodeValue.length;
  13476. }
  13477. }
  13478. // Expands the node to the closes contentEditable false element if it exists
  13479. function findParentContentEditable(node) {
  13480. var parent = node;
  13481. while (parent) {
  13482. if (parent.nodeType === 1 && getContentEditable(parent)) {
  13483. return getContentEditable(parent) === "false" ? parent : node;
  13484. }
  13485. parent = parent.parentNode;
  13486. }
  13487. return node;
  13488. }
  13489. function findWordEndPoint(container, offset, start) {
  13490. var walker, node, pos, lastTextNode;
  13491. function findSpace(node, offset) {
  13492. var pos, pos2, str = node.nodeValue;
  13493. if (typeof(offset) == "undefined") {
  13494. offset = start ? str.length : 0;
  13495. }
  13496. if (start) {
  13497. pos = str.lastIndexOf(' ', offset);
  13498. pos2 = str.lastIndexOf('\u00a0', offset);
  13499. pos = pos > pos2 ? pos : pos2;
  13500. // Include the space on remove to avoid tag soup
  13501. if (pos !== -1 && !remove) {
  13502. pos++;
  13503. }
  13504. } else {
  13505. pos = str.indexOf(' ', offset);
  13506. pos2 = str.indexOf('\u00a0', offset);
  13507. pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
  13508. }
  13509. return pos;
  13510. }
  13511. if (container.nodeType === 3) {
  13512. pos = findSpace(container, offset);
  13513. if (pos !== -1) {
  13514. return {container: container, offset: pos};
  13515. }
  13516. lastTextNode = container;
  13517. }
  13518. // Walk the nodes inside the block
  13519. walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
  13520. while ((node = walker[start ? 'prev' : 'next']())) {
  13521. if (node.nodeType === 3) {
  13522. lastTextNode = node;
  13523. pos = findSpace(node);
  13524. if (pos !== -1) {
  13525. return {container: node, offset: pos};
  13526. }
  13527. } else if (isBlock(node)) {
  13528. break;
  13529. }
  13530. }
  13531. if (lastTextNode) {
  13532. if (start) {
  13533. offset = 0;
  13534. } else {
  13535. offset = lastTextNode.length;
  13536. }
  13537. return {container: lastTextNode, offset: offset};
  13538. }
  13539. }
  13540. function findSelectorEndPoint(container, sibling_name) {
  13541. var parents, i, y, curFormat;
  13542. if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
  13543. container = container[sibling_name];
  13544. }
  13545. parents = getParents(container);
  13546. for (i = 0; i < parents.length; i++) {
  13547. for (y = 0; y < format.length; y++) {
  13548. curFormat = format[y];
  13549. // If collapsed state is set then skip formats that doesn't match that
  13550. if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
  13551. continue;
  13552. }
  13553. if (dom.is(parents[i], curFormat.selector)) {
  13554. return parents[i];
  13555. }
  13556. }
  13557. }
  13558. return container;
  13559. }
  13560. function findBlockEndPoint(container, sibling_name) {
  13561. var node, root = dom.getRoot();
  13562. // Expand to block of similar type
  13563. if (!format[0].wrapper) {
  13564. node = dom.getParent(container, format[0].block, root);
  13565. }
  13566. // Expand to first wrappable block element or any block element
  13567. if (!node) {
  13568. node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
  13569. // Fixes #6183 where it would expand to editable parent element in inline mode
  13570. return node != root && isTextBlock(node);
  13571. });
  13572. }
  13573. // Exclude inner lists from wrapping
  13574. if (node && format[0].wrapper) {
  13575. node = getParents(node, 'ul,ol').reverse()[0] || node;
  13576. }
  13577. // Didn't find a block element look for first/last wrappable element
  13578. if (!node) {
  13579. node = container;
  13580. while (node[sibling_name] && !isBlock(node[sibling_name])) {
  13581. node = node[sibling_name];
  13582. // Break on BR but include it will be removed later on
  13583. // we can't remove it now since we need to check if it can be wrapped
  13584. if (isEq(node, 'br')) {
  13585. break;
  13586. }
  13587. }
  13588. }
  13589. return node || container;
  13590. }
  13591. // Expand to closest contentEditable element
  13592. startContainer = findParentContentEditable(startContainer);
  13593. endContainer = findParentContentEditable(endContainer);
  13594. // Exclude bookmark nodes if possible
  13595. if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
  13596. startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
  13597. startContainer = startContainer.nextSibling || startContainer;
  13598. if (startContainer.nodeType == 3) {
  13599. startOffset = 0;
  13600. }
  13601. }
  13602. if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
  13603. endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
  13604. endContainer = endContainer.previousSibling || endContainer;
  13605. if (endContainer.nodeType == 3) {
  13606. endOffset = endContainer.length;
  13607. }
  13608. }
  13609. if (format[0].inline) {
  13610. if (rng.collapsed) {
  13611. // Expand left to closest word boundary
  13612. endPoint = findWordEndPoint(startContainer, startOffset, true);
  13613. if (endPoint) {
  13614. startContainer = endPoint.container;
  13615. startOffset = endPoint.offset;
  13616. }
  13617. // Expand right to closest word boundary
  13618. endPoint = findWordEndPoint(endContainer, endOffset);
  13619. if (endPoint) {
  13620. endContainer = endPoint.container;
  13621. endOffset = endPoint.offset;
  13622. }
  13623. }
  13624. // Avoid applying formatting to a trailing space.
  13625. leaf = findLeaf(endContainer, endOffset);
  13626. if (leaf.node) {
  13627. while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
  13628. leaf = findLeaf(leaf.node.previousSibling);
  13629. }
  13630. if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
  13631. leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
  13632. if (leaf.offset > 1) {
  13633. endContainer = leaf.node;
  13634. endContainer.splitText(leaf.offset - 1);
  13635. }
  13636. }
  13637. }
  13638. }
  13639. // Move start/end point up the tree if the leaves are sharp and if we are in different containers
  13640. // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
  13641. // This will reduce the number of wrapper elements that needs to be created
  13642. // Move start point up the tree
  13643. if (format[0].inline || format[0].block_expand) {
  13644. if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
  13645. startContainer = findParentContainer(true);
  13646. }
  13647. if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
  13648. endContainer = findParentContainer();
  13649. }
  13650. }
  13651. // Expand start/end container to matching selector
  13652. if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
  13653. // Find new startContainer/endContainer if there is better one
  13654. startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
  13655. endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
  13656. }
  13657. // Expand start/end container to matching block element or text node
  13658. if (format[0].block || format[0].selector) {
  13659. // Find new startContainer/endContainer if there is better one
  13660. startContainer = findBlockEndPoint(startContainer, 'previousSibling');
  13661. endContainer = findBlockEndPoint(endContainer, 'nextSibling');
  13662. // Non block element then try to expand up the leaf
  13663. if (format[0].block) {
  13664. if (!isBlock(startContainer)) {
  13665. startContainer = findParentContainer(true);
  13666. }
  13667. if (!isBlock(endContainer)) {
  13668. endContainer = findParentContainer();
  13669. }
  13670. }
  13671. }
  13672. // Setup index for startContainer
  13673. if (startContainer.nodeType == 1) {
  13674. startOffset = nodeIndex(startContainer);
  13675. startContainer = startContainer.parentNode;
  13676. }
  13677. // Setup index for endContainer
  13678. if (endContainer.nodeType == 1) {
  13679. endOffset = nodeIndex(endContainer) + 1;
  13680. endContainer = endContainer.parentNode;
  13681. }
  13682. // Return new range like object
  13683. return {
  13684. startContainer: startContainer,
  13685. startOffset: startOffset,
  13686. endContainer: endContainer,
  13687. endOffset: endOffset
  13688. };
  13689. }
  13690. /**
  13691. * Removes the specified format for the specified node. It will also remove the node if it doesn't have
  13692. * any attributes if the format specifies it to do so.
  13693. *
  13694. * @private
  13695. * @param {Object} format Format object with items to remove from node.
  13696. * @param {Object} vars Name/value object with variables to apply to format.
  13697. * @param {Node} node Node to remove the format styles on.
  13698. * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
  13699. * @return {Boolean} True/false if the node was removed or not.
  13700. */
  13701. function removeFormat(format, vars, node, compare_node) {
  13702. var i, attrs, stylesModified;
  13703. // Check if node matches format
  13704. if (!matchName(node, format)) {
  13705. return FALSE;
  13706. }
  13707. // Should we compare with format attribs and styles
  13708. if (format.remove != 'all') {
  13709. // Remove styles
  13710. each(format.styles, function(value, name) {
  13711. value = normalizeStyleValue(replaceVars(value, vars), name);
  13712. // Indexed array
  13713. if (typeof(name) === 'number') {
  13714. name = value;
  13715. compare_node = 0;
  13716. }
  13717. if (!compare_node || isEq(getStyle(compare_node, name), value)) {
  13718. dom.setStyle(node, name, '');
  13719. }
  13720. stylesModified = 1;
  13721. });
  13722. // Remove style attribute if it's empty
  13723. if (stylesModified && dom.getAttrib(node, 'style') === '') {
  13724. node.removeAttribute('style');
  13725. node.removeAttribute('data-mce-style');
  13726. }
  13727. // Remove attributes
  13728. each(format.attributes, function(value, name) {
  13729. var valueOut;
  13730. value = replaceVars(value, vars);
  13731. // Indexed array
  13732. if (typeof(name) === 'number') {
  13733. name = value;
  13734. compare_node = 0;
  13735. }
  13736. if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
  13737. // Keep internal classes
  13738. if (name == 'class') {
  13739. value = dom.getAttrib(node, name);
  13740. if (value) {
  13741. // Build new class value where everything is removed except the internal prefixed classes
  13742. valueOut = '';
  13743. each(value.split(/\s+/), function(cls) {
  13744. if (/mce\w+/.test(cls)) {
  13745. valueOut += (valueOut ? ' ' : '') + cls;
  13746. }
  13747. });
  13748. // We got some internal classes left
  13749. if (valueOut) {
  13750. dom.setAttrib(node, name, valueOut);
  13751. return;
  13752. }
  13753. }
  13754. }
  13755. // IE6 has a bug where the attribute doesn't get removed correctly
  13756. if (name == "class") {
  13757. node.removeAttribute('className');
  13758. }
  13759. // Remove mce prefixed attributes
  13760. if (MCE_ATTR_RE.test(name)) {
  13761. node.removeAttribute('data-mce-' + name);
  13762. }
  13763. node.removeAttribute(name);
  13764. }
  13765. });
  13766. // Remove classes
  13767. each(format.classes, function(value) {
  13768. value = replaceVars(value, vars);
  13769. if (!compare_node || dom.hasClass(compare_node, value)) {
  13770. dom.removeClass(node, value);
  13771. }
  13772. });
  13773. // Check for non internal attributes
  13774. attrs = dom.getAttribs(node);
  13775. for (i = 0; i < attrs.length; i++) {
  13776. if (attrs[i].nodeName.indexOf('_') !== 0) {
  13777. return FALSE;
  13778. }
  13779. }
  13780. }
  13781. // Remove the inline child if it's empty for example <b> or <span>
  13782. if (format.remove != 'none') {
  13783. removeNode(node, format);
  13784. return TRUE;
  13785. }
  13786. }
  13787. /**
  13788. * Removes the node and wrap it's children in paragraphs before doing so or
  13789. * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
  13790. *
  13791. * If the div in the node below gets removed:
  13792. * text<div>text</div>text
  13793. *
  13794. * Output becomes:
  13795. * text<div><br />text<br /></div>text
  13796. *
  13797. * So when the div is removed the result is:
  13798. * text<br />text<br />text
  13799. *
  13800. * @private
  13801. * @param {Node} node Node to remove + apply BR/P elements to.
  13802. * @param {Object} format Format rule.
  13803. * @return {Node} Input node.
  13804. */
  13805. function removeNode(node, format) {
  13806. var parentNode = node.parentNode, rootBlockElm;
  13807. function find(node, next, inc) {
  13808. node = getNonWhiteSpaceSibling(node, next, inc);
  13809. return !node || (node.nodeName == 'BR' || isBlock(node));
  13810. }
  13811. if (format.block) {
  13812. if (!forcedRootBlock) {
  13813. // Append BR elements if needed before we remove the block
  13814. if (isBlock(node) && !isBlock(parentNode)) {
  13815. if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
  13816. node.insertBefore(dom.create('br'), node.firstChild);
  13817. }
  13818. if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
  13819. node.appendChild(dom.create('br'));
  13820. }
  13821. }
  13822. } else {
  13823. // Wrap the block in a forcedRootBlock if we are at the root of document
  13824. if (parentNode == dom.getRoot()) {
  13825. if (!format.list_block || !isEq(node, format.list_block)) {
  13826. each(grep(node.childNodes), function(node) {
  13827. if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
  13828. if (!rootBlockElm) {
  13829. rootBlockElm = wrap(node, forcedRootBlock);
  13830. dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
  13831. } else {
  13832. rootBlockElm.appendChild(node);
  13833. }
  13834. } else {
  13835. rootBlockElm = 0;
  13836. }
  13837. });
  13838. }
  13839. }
  13840. }
  13841. }
  13842. // Never remove nodes that isn't the specified inline element if a selector is specified too
  13843. if (format.selector && format.inline && !isEq(format.inline, node)) {
  13844. return;
  13845. }
  13846. dom.remove(node, 1);
  13847. }
  13848. /**
  13849. * Returns the next/previous non whitespace node.
  13850. *
  13851. * @private
  13852. * @param {Node} node Node to start at.
  13853. * @param {boolean} next (Optional) Include next or previous node defaults to previous.
  13854. * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
  13855. * @return {Node} Next or previous node or undefined if it wasn't found.
  13856. */
  13857. function getNonWhiteSpaceSibling(node, next, inc) {
  13858. if (node) {
  13859. next = next ? 'nextSibling' : 'previousSibling';
  13860. for (node = inc ? node : node[next]; node; node = node[next]) {
  13861. if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
  13862. return node;
  13863. }
  13864. }
  13865. }
  13866. }
  13867. /**
  13868. * Checks if the specified node is a bookmark node or not.
  13869. *
  13870. * @private
  13871. * @param {Node} node Node to check if it's a bookmark node or not.
  13872. * @return {Boolean} true/false if the node is a bookmark node.
  13873. */
  13874. function isBookmarkNode(node) {
  13875. return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
  13876. }
  13877. /**
  13878. * Merges the next/previous sibling element if they match.
  13879. *
  13880. * @private
  13881. * @param {Node} prev Previous node to compare/merge.
  13882. * @param {Node} next Next node to compare/merge.
  13883. * @return {Node} Next node if we didn't merge and prev node if we did.
  13884. */
  13885. function mergeSiblings(prev, next) {
  13886. var sibling, tmpSibling;
  13887. /**
  13888. * Compares two nodes and checks if it's attributes and styles matches.
  13889. * This doesn't compare classes as items since their order is significant.
  13890. *
  13891. * @private
  13892. * @param {Node} node1 First node to compare with.
  13893. * @param {Node} node2 Second node to compare with.
  13894. * @return {boolean} True/false if the nodes are the same or not.
  13895. */
  13896. function compareElements(node1, node2) {
  13897. // Not the same name
  13898. if (node1.nodeName != node2.nodeName) {
  13899. return FALSE;
  13900. }
  13901. /**
  13902. * Returns all the nodes attributes excluding internal ones, styles and classes.
  13903. *
  13904. * @private
  13905. * @param {Node} node Node to get attributes from.
  13906. * @return {Object} Name/value object with attributes and attribute values.
  13907. */
  13908. function getAttribs(node) {
  13909. var attribs = {};
  13910. each(dom.getAttribs(node), function(attr) {
  13911. var name = attr.nodeName.toLowerCase();
  13912. // Don't compare internal attributes or style
  13913. if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
  13914. attribs[name] = dom.getAttrib(node, name);
  13915. }
  13916. });
  13917. return attribs;
  13918. }
  13919. /**
  13920. * Compares two objects checks if it's key + value exists in the other one.
  13921. *
  13922. * @private
  13923. * @param {Object} obj1 First object to compare.
  13924. * @param {Object} obj2 Second object to compare.
  13925. * @return {boolean} True/false if the objects matches or not.
  13926. */
  13927. function compareObjects(obj1, obj2) {
  13928. var value, name;
  13929. for (name in obj1) {
  13930. // Obj1 has item obj2 doesn't have
  13931. if (obj1.hasOwnProperty(name)) {
  13932. value = obj2[name];
  13933. // Obj2 doesn't have obj1 item
  13934. if (value === undef) {
  13935. return FALSE;
  13936. }
  13937. // Obj2 item has a different value
  13938. if (obj1[name] != value) {
  13939. return FALSE;
  13940. }
  13941. // Delete similar value
  13942. delete obj2[name];
  13943. }
  13944. }
  13945. // Check if obj 2 has something obj 1 doesn't have
  13946. for (name in obj2) {
  13947. // Obj2 has item obj1 doesn't have
  13948. if (obj2.hasOwnProperty(name)) {
  13949. return FALSE;
  13950. }
  13951. }
  13952. return TRUE;
  13953. }
  13954. // Attribs are not the same
  13955. if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
  13956. return FALSE;
  13957. }
  13958. // Styles are not the same
  13959. if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
  13960. return FALSE;
  13961. }
  13962. return !isBookmarkNode(node1) && !isBookmarkNode(node2);
  13963. }
  13964. function findElementSibling(node, sibling_name) {
  13965. for (sibling = node; sibling; sibling = sibling[sibling_name]) {
  13966. if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
  13967. return node;
  13968. }
  13969. if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
  13970. return sibling;
  13971. }
  13972. }
  13973. return node;
  13974. }
  13975. // Check if next/prev exists and that they are elements
  13976. if (prev && next) {
  13977. // If previous sibling is empty then jump over it
  13978. prev = findElementSibling(prev, 'previousSibling');
  13979. next = findElementSibling(next, 'nextSibling');
  13980. // Compare next and previous nodes
  13981. if (compareElements(prev, next)) {
  13982. // Append nodes between
  13983. for (sibling = prev.nextSibling; sibling && sibling != next;) {
  13984. tmpSibling = sibling;
  13985. sibling = sibling.nextSibling;
  13986. prev.appendChild(tmpSibling);
  13987. }
  13988. // Remove next node
  13989. dom.remove(next);
  13990. // Move children into prev node
  13991. each(grep(next.childNodes), function(node) {
  13992. prev.appendChild(node);
  13993. });
  13994. return prev;
  13995. }
  13996. }
  13997. return next;
  13998. }
  13999. function getContainer(rng, start) {
  14000. var container, offset, lastIdx;
  14001. container = rng[start ? 'startContainer' : 'endContainer'];
  14002. offset = rng[start ? 'startOffset' : 'endOffset'];
  14003. if (container.nodeType == 1) {
  14004. lastIdx = container.childNodes.length - 1;
  14005. if (!start && offset) {
  14006. offset--;
  14007. }
  14008. container = container.childNodes[offset > lastIdx ? lastIdx : offset];
  14009. }
  14010. // If start text node is excluded then walk to the next node
  14011. if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
  14012. container = new TreeWalker(container, ed.getBody()).next() || container;
  14013. }
  14014. // If end text node is excluded then walk to the previous node
  14015. if (container.nodeType === 3 && !start && offset === 0) {
  14016. container = new TreeWalker(container, ed.getBody()).prev() || container;
  14017. }
  14018. return container;
  14019. }
  14020. function performCaretAction(type, name, vars) {
  14021. var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
  14022. // Creates a caret container bogus element
  14023. function createCaretContainer(fill) {
  14024. var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
  14025. if (fill) {
  14026. caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
  14027. }
  14028. return caretContainer;
  14029. }
  14030. function isCaretContainerEmpty(node, nodes) {
  14031. while (node) {
  14032. if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
  14033. return false;
  14034. }
  14035. // Collect nodes
  14036. if (nodes && node.nodeType === 1) {
  14037. nodes.push(node);
  14038. }
  14039. node = node.firstChild;
  14040. }
  14041. return true;
  14042. }
  14043. // Returns any parent caret container element
  14044. function getParentCaretContainer(node) {
  14045. while (node) {
  14046. if (node.id === caretContainerId) {
  14047. return node;
  14048. }
  14049. node = node.parentNode;
  14050. }
  14051. }
  14052. // Finds the first text node in the specified node
  14053. function findFirstTextNode(node) {
  14054. var walker;
  14055. if (node) {
  14056. walker = new TreeWalker(node, node);
  14057. for (node = walker.current(); node; node = walker.next()) {
  14058. if (node.nodeType === 3) {
  14059. return node;
  14060. }
  14061. }
  14062. }
  14063. }
  14064. // Removes the caret container for the specified node or all on the current document
  14065. function removeCaretContainer(node, move_caret) {
  14066. var child, rng;
  14067. if (!node) {
  14068. node = getParentCaretContainer(selection.getStart());
  14069. if (!node) {
  14070. while ((node = dom.get(caretContainerId))) {
  14071. removeCaretContainer(node, false);
  14072. }
  14073. }
  14074. } else {
  14075. rng = selection.getRng(true);
  14076. if (isCaretContainerEmpty(node)) {
  14077. if (move_caret !== false) {
  14078. rng.setStartBefore(node);
  14079. rng.setEndBefore(node);
  14080. }
  14081. dom.remove(node);
  14082. } else {
  14083. child = findFirstTextNode(node);
  14084. if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
  14085. child = child.deleteData(0, 1);
  14086. }
  14087. dom.remove(node, 1);
  14088. }
  14089. selection.setRng(rng);
  14090. }
  14091. }
  14092. // Applies formatting to the caret postion
  14093. function applyCaretFormat() {
  14094. var rng, caretContainer, textNode, offset, bookmark, container, text;
  14095. rng = selection.getRng(true);
  14096. offset = rng.startOffset;
  14097. container = rng.startContainer;
  14098. text = container.nodeValue;
  14099. caretContainer = getParentCaretContainer(selection.getStart());
  14100. if (caretContainer) {
  14101. textNode = findFirstTextNode(caretContainer);
  14102. }
  14103. // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
  14104. if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
  14105. // Get bookmark of caret position
  14106. bookmark = selection.getBookmark();
  14107. // Collapse bookmark range (WebKit)
  14108. rng.collapse(true);
  14109. // Expand the range to the closest word and split it at those points
  14110. rng = expandRng(rng, get(name));
  14111. rng = rangeUtils.split(rng);
  14112. // Apply the format to the range
  14113. apply(name, vars, rng);
  14114. // Move selection back to caret position
  14115. selection.moveToBookmark(bookmark);
  14116. } else {
  14117. if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
  14118. caretContainer = createCaretContainer(true);
  14119. textNode = caretContainer.firstChild;
  14120. rng.insertNode(caretContainer);
  14121. offset = 1;
  14122. apply(name, vars, caretContainer);
  14123. } else {
  14124. apply(name, vars, caretContainer);
  14125. }
  14126. // Move selection to text node
  14127. selection.setCursorLocation(textNode, offset);
  14128. }
  14129. }
  14130. function removeCaretFormat() {
  14131. var rng = selection.getRng(true), container, offset, bookmark,
  14132. hasContentAfter, node, formatNode, parents = [], i, caretContainer;
  14133. container = rng.startContainer;
  14134. offset = rng.startOffset;
  14135. node = container;
  14136. if (container.nodeType == 3) {
  14137. if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
  14138. hasContentAfter = true;
  14139. }
  14140. node = node.parentNode;
  14141. }
  14142. while (node) {
  14143. if (matchNode(node, name, vars)) {
  14144. formatNode = node;
  14145. break;
  14146. }
  14147. if (node.nextSibling) {
  14148. hasContentAfter = true;
  14149. }
  14150. parents.push(node);
  14151. node = node.parentNode;
  14152. }
  14153. // Node doesn't have the specified format
  14154. if (!formatNode) {
  14155. return;
  14156. }
  14157. // Is there contents after the caret then remove the format on the element
  14158. if (hasContentAfter) {
  14159. // Get bookmark of caret position
  14160. bookmark = selection.getBookmark();
  14161. // Collapse bookmark range (WebKit)
  14162. rng.collapse(true);
  14163. // Expand the range to the closest word and split it at those points
  14164. rng = expandRng(rng, get(name), true);
  14165. rng = rangeUtils.split(rng);
  14166. // Remove the format from the range
  14167. remove(name, vars, rng);
  14168. // Move selection back to caret position
  14169. selection.moveToBookmark(bookmark);
  14170. } else {
  14171. caretContainer = createCaretContainer();
  14172. node = caretContainer;
  14173. for (i = parents.length - 1; i >= 0; i--) {
  14174. node.appendChild(dom.clone(parents[i], false));
  14175. node = node.firstChild;
  14176. }
  14177. // Insert invisible character into inner most format element
  14178. node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
  14179. node = node.firstChild;
  14180. var block = dom.getParent(formatNode, isTextBlock);
  14181. if (block && dom.isEmpty(block)) {
  14182. // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
  14183. formatNode.parentNode.replaceChild(caretContainer, formatNode);
  14184. } else {
  14185. // Insert caret container after the formated node
  14186. dom.insertAfter(caretContainer, formatNode);
  14187. }
  14188. // Move selection to text node
  14189. selection.setCursorLocation(node, 1);
  14190. // If the formatNode is empty, we can remove it safely.
  14191. if (dom.isEmpty(formatNode)) {
  14192. dom.remove(formatNode);
  14193. }
  14194. }
  14195. }
  14196. // Checks if the parent caret container node isn't empty if that is the case it
  14197. // will remove the bogus state on all children that isn't empty
  14198. function unmarkBogusCaretParents() {
  14199. var caretContainer;
  14200. caretContainer = getParentCaretContainer(selection.getStart());
  14201. if (caretContainer && !dom.isEmpty(caretContainer)) {
  14202. walk(caretContainer, function(node) {
  14203. if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
  14204. dom.setAttrib(node, 'data-mce-bogus', null);
  14205. }
  14206. }, 'childNodes');
  14207. }
  14208. }
  14209. // Only bind the caret events once
  14210. if (!ed._hasCaretEvents) {
  14211. // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
  14212. markCaretContainersBogus = function() {
  14213. var nodes = [], i;
  14214. if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
  14215. // Mark children
  14216. i = nodes.length;
  14217. while (i--) {
  14218. dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
  14219. }
  14220. }
  14221. };
  14222. disableCaretContainer = function(e) {
  14223. var keyCode = e.keyCode;
  14224. removeCaretContainer();
  14225. // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
  14226. if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
  14227. removeCaretContainer(getParentCaretContainer(selection.getStart()));
  14228. }
  14229. unmarkBogusCaretParents();
  14230. };
  14231. // Remove bogus state if they got filled by contents using editor.selection.setContent
  14232. ed.on('SetContent', function(e) {
  14233. if (e.selection) {
  14234. unmarkBogusCaretParents();
  14235. }
  14236. });
  14237. ed._hasCaretEvents = true;
  14238. }
  14239. // Do apply or remove caret format
  14240. if (type == "apply") {
  14241. applyCaretFormat();
  14242. } else {
  14243. removeCaretFormat();
  14244. }
  14245. }
  14246. /**
  14247. * Moves the start to the first suitable text node.
  14248. */
  14249. function moveStart(rng) {
  14250. var container = rng.startContainer,
  14251. offset = rng.startOffset, isAtEndOfText,
  14252. walker, node, nodes, tmpNode;
  14253. // Convert text node into index if possible
  14254. if (container.nodeType == 3 && offset >= container.nodeValue.length) {
  14255. // Get the parent container location and walk from there
  14256. offset = nodeIndex(container);
  14257. container = container.parentNode;
  14258. isAtEndOfText = true;
  14259. }
  14260. // Move startContainer/startOffset in to a suitable node
  14261. if (container.nodeType == 1) {
  14262. nodes = container.childNodes;
  14263. container = nodes[Math.min(offset, nodes.length - 1)];
  14264. walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
  14265. // If offset is at end of the parent node walk to the next one
  14266. if (offset > nodes.length - 1 || isAtEndOfText) {
  14267. walker.next();
  14268. }
  14269. for (node = walker.current(); node; node = walker.next()) {
  14270. if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
  14271. // IE has a "neat" feature where it moves the start node into the closest element
  14272. // we can avoid this by inserting an element before it and then remove it after we set the selection
  14273. tmpNode = dom.create('a', null, INVISIBLE_CHAR);
  14274. node.parentNode.insertBefore(tmpNode, node);
  14275. // Set selection and remove tmpNode
  14276. rng.setStart(node, 0);
  14277. selection.setRng(rng);
  14278. dom.remove(tmpNode);
  14279. return;
  14280. }
  14281. }
  14282. }
  14283. }
  14284. };
  14285. });
  14286. // Included from: js/tinymce/classes/UndoManager.js
  14287. /**
  14288. * UndoManager.js
  14289. *
  14290. * Copyright, Moxiecode Systems AB
  14291. * Released under LGPL License.
  14292. *
  14293. * License: http://www.tinymce.com/license
  14294. * Contributing: http://www.tinymce.com/contributing
  14295. */
  14296. /**
  14297. * This class handles the undo/redo history levels for the editor. Since the build in undo/redo has major drawbacks a custom one was needed.
  14298. *
  14299. * @class tinymce.UndoManager
  14300. */
  14301. define("tinymce/UndoManager", [
  14302. "tinymce/Env",
  14303. "tinymce/util/Tools"
  14304. ], function(Env, Tools) {
  14305. var trim = Tools.trim, trimContentRegExp;
  14306. trimContentRegExp = new RegExp([
  14307. '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
  14308. '<div[^>]+data-mce-bogus[^>]+><\\/div>', // Trim bogus divs like resize handles
  14309. '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
  14310. ].join('|'), 'gi');
  14311. return function(editor) {
  14312. var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
  14313. // Returns a trimmed version of the current editor contents
  14314. function getContent() {
  14315. return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
  14316. }
  14317. function addNonTypingUndoLevel(e) {
  14318. self.typing = false;
  14319. self.add({}, e);
  14320. }
  14321. // Add initial undo level when the editor is initialized
  14322. editor.on('init', function() {
  14323. self.add();
  14324. });
  14325. // Get position before an execCommand is processed
  14326. editor.on('BeforeExecCommand', function(e) {
  14327. var cmd = e.command;
  14328. if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  14329. self.beforeChange();
  14330. }
  14331. });
  14332. // Add undo level after an execCommand call was made
  14333. editor.on('ExecCommand', function(e) {
  14334. var cmd = e.command;
  14335. if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  14336. addNonTypingUndoLevel(e);
  14337. }
  14338. });
  14339. editor.on('ObjectResizeStart', function() {
  14340. self.beforeChange();
  14341. });
  14342. editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
  14343. editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
  14344. editor.on('KeyUp', function(e) {
  14345. var keyCode = e.keyCode;
  14346. if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
  14347. addNonTypingUndoLevel();
  14348. editor.nodeChanged();
  14349. }
  14350. if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
  14351. editor.nodeChanged();
  14352. }
  14353. // Fire a TypingUndo event on the first character entered
  14354. if (isFirstTypedCharacter && self.typing) {
  14355. // Make the it dirty if the content was changed after typing the first character
  14356. if (!editor.isDirty()) {
  14357. editor.isNotDirty = !data[0] || getContent() == data[0].content;
  14358. // Fire initial change event
  14359. if (!editor.isNotDirty) {
  14360. editor.fire('change', {level: data[0], lastLevel: null});
  14361. }
  14362. }
  14363. editor.fire('TypingUndo');
  14364. isFirstTypedCharacter = false;
  14365. editor.nodeChanged();
  14366. }
  14367. });
  14368. editor.on('KeyDown', function(e) {
  14369. var keyCode = e.keyCode;
  14370. // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
  14371. if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
  14372. if (self.typing) {
  14373. addNonTypingUndoLevel(e);
  14374. }
  14375. return;
  14376. }
  14377. // If key isn't shift,ctrl,alt,capslock,metakey
  14378. if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
  14379. self.beforeChange();
  14380. self.typing = true;
  14381. self.add({}, e);
  14382. isFirstTypedCharacter = true;
  14383. }
  14384. });
  14385. editor.on('MouseDown', function(e) {
  14386. if (self.typing) {
  14387. addNonTypingUndoLevel(e);
  14388. }
  14389. });
  14390. // Add keyboard shortcuts for undo/redo keys
  14391. editor.addShortcut('ctrl+z', '', 'Undo');
  14392. editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');
  14393. editor.on('AddUndo Undo Redo ClearUndos MouseUp', function(e) {
  14394. if (!e.isDefaultPrevented()) {
  14395. editor.nodeChanged();
  14396. }
  14397. });
  14398. self = {
  14399. // Explose for debugging reasons
  14400. data: data,
  14401. /**
  14402. * State if the user is currently typing or not. This will add a typing operation into one undo
  14403. * level instead of one new level for each keystroke.
  14404. *
  14405. * @field {Boolean} typing
  14406. */
  14407. typing: false,
  14408. /**
  14409. * Stores away a bookmark to be used when performing an undo action so that the selection is before
  14410. * the change has been made.
  14411. *
  14412. * @method beforeChange
  14413. */
  14414. beforeChange: function() {
  14415. if (!locks) {
  14416. beforeBookmark = editor.selection.getBookmark(2, true);
  14417. }
  14418. },
  14419. /**
  14420. * Adds a new undo level/snapshot to the undo list.
  14421. *
  14422. * @method add
  14423. * @param {Object} level Optional undo level object to add.
  14424. * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
  14425. * @return {Object} Undo level that got added or null it a level wasn't needed.
  14426. */
  14427. add: function(level, event) {
  14428. var i, settings = editor.settings, lastLevel;
  14429. level = level || {};
  14430. level.content = getContent();
  14431. if (locks || editor.removed) {
  14432. return null;
  14433. }
  14434. lastLevel = data[index];
  14435. if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
  14436. return null;
  14437. }
  14438. // Add undo level if needed
  14439. if (lastLevel && lastLevel.content == level.content) {
  14440. return null;
  14441. }
  14442. // Set before bookmark on previous level
  14443. if (data[index]) {
  14444. data[index].beforeBookmark = beforeBookmark;
  14445. }
  14446. // Time to compress
  14447. if (settings.custom_undo_redo_levels) {
  14448. if (data.length > settings.custom_undo_redo_levels) {
  14449. for (i = 0; i < data.length - 1; i++) {
  14450. data[i] = data[i + 1];
  14451. }
  14452. data.length--;
  14453. index = data.length;
  14454. }
  14455. }
  14456. // Get a non intrusive normalized bookmark
  14457. level.bookmark = editor.selection.getBookmark(2, true);
  14458. // Crop array if needed
  14459. if (index < data.length - 1) {
  14460. data.length = index + 1;
  14461. }
  14462. data.push(level);
  14463. index = data.length - 1;
  14464. var args = {level: level, lastLevel: lastLevel, originalEvent: event};
  14465. editor.fire('AddUndo', args);
  14466. if (index > 0) {
  14467. editor.isNotDirty = false;
  14468. editor.fire('change', args);
  14469. }
  14470. return level;
  14471. },
  14472. /**
  14473. * Undoes the last action.
  14474. *
  14475. * @method undo
  14476. * @return {Object} Undo level or null if no undo was performed.
  14477. */
  14478. undo: function() {
  14479. var level;
  14480. if (self.typing) {
  14481. self.add();
  14482. self.typing = false;
  14483. }
  14484. if (index > 0) {
  14485. level = data[--index];
  14486. // Undo to first index then set dirty state to false
  14487. if (index === 0) {
  14488. editor.isNotDirty = true;
  14489. }
  14490. editor.setContent(level.content, {format: 'raw'});
  14491. editor.selection.moveToBookmark(level.beforeBookmark);
  14492. editor.fire('undo', {level: level});
  14493. }
  14494. return level;
  14495. },
  14496. /**
  14497. * Redoes the last action.
  14498. *
  14499. * @method redo
  14500. * @return {Object} Redo level or null if no redo was performed.
  14501. */
  14502. redo: function() {
  14503. var level;
  14504. if (index < data.length - 1) {
  14505. level = data[++index];
  14506. editor.setContent(level.content, {format: 'raw'});
  14507. editor.selection.moveToBookmark(level.bookmark);
  14508. editor.fire('redo', {level: level});
  14509. }
  14510. return level;
  14511. },
  14512. /**
  14513. * Removes all undo levels.
  14514. *
  14515. * @method clear
  14516. */
  14517. clear: function() {
  14518. data = [];
  14519. index = 0;
  14520. self.typing = false;
  14521. editor.fire('ClearUndos');
  14522. },
  14523. /**
  14524. * Returns true/false if the undo manager has any undo levels.
  14525. *
  14526. * @method hasUndo
  14527. * @return {Boolean} true/false if the undo manager has any undo levels.
  14528. */
  14529. hasUndo: function() {
  14530. // Has undo levels or typing and content isn't the same as the initial level
  14531. return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
  14532. },
  14533. /**
  14534. * Returns true/false if the undo manager has any redo levels.
  14535. *
  14536. * @method hasRedo
  14537. * @return {Boolean} true/false if the undo manager has any redo levels.
  14538. */
  14539. hasRedo: function() {
  14540. return index < data.length - 1 && !this.typing;
  14541. },
  14542. /**
  14543. * Executes the specified function in an undo transation. The selection
  14544. * before the modification will be stored to the undo stack and if the DOM changes
  14545. * it will add a new undo level. Any methods within the transation that adds undo levels will
  14546. * be ignored. So a transation can include calls to execCommand or editor.insertContent.
  14547. *
  14548. * @method transact
  14549. * @param {function} callback Function to execute dom manipulation logic in.
  14550. */
  14551. transact: function(callback) {
  14552. self.beforeChange();
  14553. try {
  14554. locks++;
  14555. callback();
  14556. } finally {
  14557. locks--;
  14558. }
  14559. self.add();
  14560. }
  14561. };
  14562. return self;
  14563. };
  14564. });
  14565. // Included from: js/tinymce/classes/EnterKey.js
  14566. /**
  14567. * EnterKey.js
  14568. *
  14569. * Copyright, Moxiecode Systems AB
  14570. * Released under LGPL License.
  14571. *
  14572. * License: http://www.tinymce.com/license
  14573. * Contributing: http://www.tinymce.com/contributing
  14574. */
  14575. /**
  14576. * Contains logic for handling the enter key to split/generate block elements.
  14577. */
  14578. define("tinymce/EnterKey", [
  14579. "tinymce/dom/TreeWalker",
  14580. "tinymce/dom/RangeUtils",
  14581. "tinymce/Env"
  14582. ], function(TreeWalker, RangeUtils, Env) {
  14583. var isIE = Env.ie && Env.ie < 11;
  14584. return function(editor) {
  14585. var dom = editor.dom, selection = editor.selection, settings = editor.settings;
  14586. var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
  14587. function handleEnterKey(evt) {
  14588. var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
  14589. newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
  14590. // Returns true if the block can be split into two blocks or not
  14591. function canSplitBlock(node) {
  14592. return node &&
  14593. dom.isBlock(node) &&
  14594. !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
  14595. !/^(fixed|absolute)/i.test(node.style.position) &&
  14596. dom.getContentEditable(node) !== "true";
  14597. }
  14598. // Renders empty block on IE
  14599. function renderBlockOnIE(block) {
  14600. var oldRng;
  14601. if (dom.isBlock(block)) {
  14602. oldRng = selection.getRng();
  14603. block.appendChild(dom.create('span', null, '\u00a0'));
  14604. selection.select(block);
  14605. block.lastChild.outerHTML = '';
  14606. selection.setRng(oldRng);
  14607. }
  14608. }
  14609. // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
  14610. function trimInlineElementsOnLeftSideOfBlock(block) {
  14611. var node = block, firstChilds = [], i;
  14612. // Find inner most first child ex: <p><i><b>*</b></i></p>
  14613. while ((node = node.firstChild)) {
  14614. if (dom.isBlock(node)) {
  14615. return;
  14616. }
  14617. if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  14618. firstChilds.push(node);
  14619. }
  14620. }
  14621. i = firstChilds.length;
  14622. while (i--) {
  14623. node = firstChilds[i];
  14624. if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
  14625. dom.remove(node);
  14626. } else {
  14627. // Remove <a> </a> see #5381
  14628. if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
  14629. dom.remove(node);
  14630. }
  14631. }
  14632. }
  14633. }
  14634. // Moves the caret to a suitable position within the root for example in the first non
  14635. // pure whitespace text node or before an image
  14636. function moveToCaretPosition(root) {
  14637. var walker, node, rng, lastNode = root, tempElm;
  14638. function firstNonWhiteSpaceNodeSibling(node) {
  14639. while (node) {
  14640. if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
  14641. return node;
  14642. }
  14643. node = node.nextSibling;
  14644. }
  14645. }
  14646. // Old IE versions doesn't properly render blocks with br elements in them
  14647. // For example <p><br></p> wont be rendered correctly in a contentEditable area
  14648. // until you remove the br producing <p></p>
  14649. if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
  14650. if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
  14651. dom.remove(parentBlock.firstChild);
  14652. }
  14653. }
  14654. if (root.nodeName == 'LI') {
  14655. var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
  14656. if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) {
  14657. root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
  14658. }
  14659. }
  14660. rng = dom.createRng();
  14661. if (root.hasChildNodes()) {
  14662. walker = new TreeWalker(root, root);
  14663. while ((node = walker.current())) {
  14664. if (node.nodeType == 3) {
  14665. rng.setStart(node, 0);
  14666. rng.setEnd(node, 0);
  14667. break;
  14668. }
  14669. if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  14670. rng.setStartBefore(node);
  14671. rng.setEndBefore(node);
  14672. break;
  14673. }
  14674. lastNode = node;
  14675. node = walker.next();
  14676. }
  14677. if (!node) {
  14678. rng.setStart(lastNode, 0);
  14679. rng.setEnd(lastNode, 0);
  14680. }
  14681. } else {
  14682. if (root.nodeName == 'BR') {
  14683. if (root.nextSibling && dom.isBlock(root.nextSibling)) {
  14684. // Trick on older IE versions to render the caret before the BR between two lists
  14685. if (!documentMode || documentMode < 9) {
  14686. tempElm = dom.create('br');
  14687. root.parentNode.insertBefore(tempElm, root);
  14688. }
  14689. rng.setStartBefore(root);
  14690. rng.setEndBefore(root);
  14691. } else {
  14692. rng.setStartAfter(root);
  14693. rng.setEndAfter(root);
  14694. }
  14695. } else {
  14696. rng.setStart(root, 0);
  14697. rng.setEnd(root, 0);
  14698. }
  14699. }
  14700. selection.setRng(rng);
  14701. // Remove tempElm created for old IE:s
  14702. dom.remove(tempElm);
  14703. selection.scrollIntoView(root);
  14704. }
  14705. function setForcedBlockAttrs(node) {
  14706. var forcedRootBlockName = settings.forced_root_block;
  14707. if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
  14708. dom.setAttribs(node, settings.forced_root_block_attrs);
  14709. }
  14710. }
  14711. // Creates a new block element by cloning the current one or creating a new one if the name is specified
  14712. // This function will also copy any text formatting from the parent block and add it to the new one
  14713. function createNewBlock(name) {
  14714. var node = container, block, clonedNode, caretNode;
  14715. if (name || parentBlockName == "TABLE") {
  14716. block = dom.create(name || newBlockName);
  14717. setForcedBlockAttrs(block);
  14718. } else {
  14719. block = parentBlock.cloneNode(false);
  14720. }
  14721. caretNode = block;
  14722. // Clone any parent styles
  14723. if (settings.keep_styles !== false) {
  14724. do {
  14725. if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
  14726. // Never clone a caret containers
  14727. if (node.id == '_mce_caret') {
  14728. continue;
  14729. }
  14730. clonedNode = node.cloneNode(false);
  14731. dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
  14732. if (block.hasChildNodes()) {
  14733. clonedNode.appendChild(block.firstChild);
  14734. block.appendChild(clonedNode);
  14735. } else {
  14736. caretNode = clonedNode;
  14737. block.appendChild(clonedNode);
  14738. }
  14739. }
  14740. } while ((node = node.parentNode));
  14741. }
  14742. // BR is needed in empty blocks on non IE browsers
  14743. if (!isIE) {
  14744. caretNode.innerHTML = '<br data-mce-bogus="1">';
  14745. }
  14746. return block;
  14747. }
  14748. // Returns true/false if the caret is at the start/end of the parent block element
  14749. function isCaretAtStartOrEndOfBlock(start) {
  14750. var walker, node, name;
  14751. // Caret is in the middle of a text node like "a|b"
  14752. if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
  14753. return false;
  14754. }
  14755. // If after the last element in block node edge case for #5091
  14756. if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
  14757. return true;
  14758. }
  14759. // If the caret if before the first element in parentBlock
  14760. if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
  14761. return true;
  14762. }
  14763. // Caret can be before/after a table
  14764. if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
  14765. return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
  14766. }
  14767. // Walk the DOM and look for text nodes or non empty elements
  14768. walker = new TreeWalker(container, parentBlock);
  14769. // If caret is in beginning or end of a text block then jump to the next/previous node
  14770. if (container.nodeType == 3) {
  14771. if (start && offset === 0) {
  14772. walker.prev();
  14773. } else if (!start && offset == container.nodeValue.length) {
  14774. walker.next();
  14775. }
  14776. }
  14777. while ((node = walker.current())) {
  14778. if (node.nodeType === 1) {
  14779. // Ignore bogus elements
  14780. if (!node.getAttribute('data-mce-bogus')) {
  14781. // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
  14782. name = node.nodeName.toLowerCase();
  14783. if (nonEmptyElementsMap[name] && name !== 'br') {
  14784. return false;
  14785. }
  14786. }
  14787. } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
  14788. return false;
  14789. }
  14790. if (start) {
  14791. walker.prev();
  14792. } else {
  14793. walker.next();
  14794. }
  14795. }
  14796. return true;
  14797. }
  14798. // Wraps any text nodes or inline elements in the specified forced root block name
  14799. function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
  14800. var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
  14801. // Not in a block element or in a table cell or caption
  14802. parentBlock = dom.getParent(container, dom.isBlock);
  14803. rootBlockName = editor.getBody().nodeName.toLowerCase();
  14804. if (!parentBlock || !canSplitBlock(parentBlock)) {
  14805. parentBlock = parentBlock || editableRoot;
  14806. if (!parentBlock.hasChildNodes()) {
  14807. newBlock = dom.create(blockName);
  14808. setForcedBlockAttrs(newBlock);
  14809. parentBlock.appendChild(newBlock);
  14810. rng.setStart(newBlock, 0);
  14811. rng.setEnd(newBlock, 0);
  14812. return newBlock;
  14813. }
  14814. // Find parent that is the first child of parentBlock
  14815. node = container;
  14816. while (node.parentNode != parentBlock) {
  14817. node = node.parentNode;
  14818. }
  14819. // Loop left to find start node start wrapping at
  14820. while (node && !dom.isBlock(node)) {
  14821. startNode = node;
  14822. node = node.previousSibling;
  14823. }
  14824. if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
  14825. newBlock = dom.create(blockName);
  14826. setForcedBlockAttrs(newBlock);
  14827. startNode.parentNode.insertBefore(newBlock, startNode);
  14828. // Start wrapping until we hit a block
  14829. node = startNode;
  14830. while (node && !dom.isBlock(node)) {
  14831. next = node.nextSibling;
  14832. newBlock.appendChild(node);
  14833. node = next;
  14834. }
  14835. // Restore range to it's past location
  14836. rng.setStart(container, offset);
  14837. rng.setEnd(container, offset);
  14838. }
  14839. }
  14840. return container;
  14841. }
  14842. // Inserts a block or br before/after or in the middle of a split list of the LI is empty
  14843. function handleEmptyListItem() {
  14844. function isFirstOrLastLi(first) {
  14845. var node = containerBlock[first ? 'firstChild' : 'lastChild'];
  14846. // Find first/last element since there might be whitespace there
  14847. while (node) {
  14848. if (node.nodeType == 1) {
  14849. break;
  14850. }
  14851. node = node[first ? 'nextSibling' : 'previousSibling'];
  14852. }
  14853. return node === parentBlock;
  14854. }
  14855. function getContainerBlock() {
  14856. var containerBlockParent = containerBlock.parentNode;
  14857. if (containerBlockParent.nodeName == 'LI') {
  14858. return containerBlockParent;
  14859. }
  14860. return containerBlock;
  14861. }
  14862. // Check if we are in an nested list
  14863. var containerBlockParentName = containerBlock.parentNode.nodeName;
  14864. if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
  14865. newBlockName = 'LI';
  14866. }
  14867. newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
  14868. if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
  14869. if (containerBlockParentName == 'LI') {
  14870. // Nested list is inside a LI
  14871. dom.insertAfter(newBlock, getContainerBlock());
  14872. } else {
  14873. // Is first and last list item then replace the OL/UL with a text block
  14874. dom.replace(newBlock, containerBlock);
  14875. }
  14876. } else if (isFirstOrLastLi(true)) {
  14877. if (containerBlockParentName == 'LI') {
  14878. // List nested in an LI then move the list to a new sibling LI
  14879. dom.insertAfter(newBlock, getContainerBlock());
  14880. newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
  14881. newBlock.appendChild(containerBlock);
  14882. } else {
  14883. // First LI in list then remove LI and add text block before list
  14884. containerBlock.parentNode.insertBefore(newBlock, containerBlock);
  14885. }
  14886. } else if (isFirstOrLastLi()) {
  14887. // Last LI in list then remove LI and add text block after list
  14888. dom.insertAfter(newBlock, getContainerBlock());
  14889. renderBlockOnIE(newBlock);
  14890. } else {
  14891. // Middle LI in list the split the list and insert a text block in the middle
  14892. // Extract after fragment and insert it after the current block
  14893. containerBlock = getContainerBlock();
  14894. tmpRng = rng.cloneRange();
  14895. tmpRng.setStartAfter(parentBlock);
  14896. tmpRng.setEndAfter(containerBlock);
  14897. fragment = tmpRng.extractContents();
  14898. if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
  14899. newBlock = fragment.firstChild;
  14900. dom.insertAfter(fragment, containerBlock);
  14901. } else {
  14902. dom.insertAfter(fragment, containerBlock);
  14903. dom.insertAfter(newBlock, containerBlock);
  14904. }
  14905. }
  14906. dom.remove(parentBlock);
  14907. moveToCaretPosition(newBlock);
  14908. undoManager.add();
  14909. }
  14910. // Walks the parent block to the right and look for BR elements
  14911. function hasRightSideContent() {
  14912. var walker = new TreeWalker(container, parentBlock), node;
  14913. while ((node = walker.next())) {
  14914. if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
  14915. return true;
  14916. }
  14917. }
  14918. }
  14919. // Inserts a BR element if the forced_root_block option is set to false or empty string
  14920. function insertBr() {
  14921. var brElm, extraBr, marker;
  14922. if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
  14923. // Insert extra BR element at the end block elements
  14924. if (!isIE && !hasRightSideContent()) {
  14925. brElm = dom.create('br');
  14926. rng.insertNode(brElm);
  14927. rng.setStartAfter(brElm);
  14928. rng.setEndAfter(brElm);
  14929. extraBr = true;
  14930. }
  14931. }
  14932. brElm = dom.create('br');
  14933. rng.insertNode(brElm);
  14934. // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
  14935. if (isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
  14936. brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
  14937. }
  14938. // Insert temp marker and scroll to that
  14939. marker = dom.create('span', {}, '&nbsp;');
  14940. brElm.parentNode.insertBefore(marker, brElm);
  14941. selection.scrollIntoView(marker);
  14942. dom.remove(marker);
  14943. if (!extraBr) {
  14944. rng.setStartAfter(brElm);
  14945. rng.setEndAfter(brElm);
  14946. } else {
  14947. rng.setStartBefore(brElm);
  14948. rng.setEndBefore(brElm);
  14949. }
  14950. selection.setRng(rng);
  14951. undoManager.add();
  14952. }
  14953. // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
  14954. function trimLeadingLineBreaks(node) {
  14955. do {
  14956. if (node.nodeType === 3) {
  14957. node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
  14958. }
  14959. node = node.firstChild;
  14960. } while (node);
  14961. }
  14962. function getEditableRoot(node) {
  14963. var root = dom.getRoot(), parent, editableRoot;
  14964. // Get all parents until we hit a non editable parent or the root
  14965. parent = node;
  14966. while (parent !== root && dom.getContentEditable(parent) !== "false") {
  14967. if (dom.getContentEditable(parent) === "true") {
  14968. editableRoot = parent;
  14969. }
  14970. parent = parent.parentNode;
  14971. }
  14972. return parent !== root ? editableRoot : root;
  14973. }
  14974. // Adds a BR at the end of blocks that only contains an IMG or INPUT since
  14975. // these might be floated and then they won't expand the block
  14976. function addBrToBlockIfNeeded(block) {
  14977. var lastChild;
  14978. // IE will render the blocks correctly other browsers needs a BR
  14979. if (!isIE) {
  14980. block.normalize(); // Remove empty text nodes that got left behind by the extract
  14981. // Check if the block is empty or contains a floated last child
  14982. lastChild = block.lastChild;
  14983. if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
  14984. dom.add(block, 'br');
  14985. }
  14986. }
  14987. }
  14988. rng = selection.getRng(true);
  14989. // Event is blocked by some other handler for example the lists plugin
  14990. if (evt.isDefaultPrevented()) {
  14991. return;
  14992. }
  14993. // Delete any selected contents
  14994. if (!rng.collapsed) {
  14995. editor.execCommand('Delete');
  14996. return;
  14997. }
  14998. // Setup range items and newBlockName
  14999. new RangeUtils(dom).normalize(rng);
  15000. container = rng.startContainer;
  15001. offset = rng.startOffset;
  15002. newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
  15003. newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
  15004. documentMode = dom.doc.documentMode;
  15005. shiftKey = evt.shiftKey;
  15006. // Resolve node index
  15007. if (container.nodeType == 1 && container.hasChildNodes()) {
  15008. isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  15009. container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  15010. if (isAfterLastNodeInContainer && container.nodeType == 3) {
  15011. offset = container.nodeValue.length;
  15012. } else {
  15013. offset = 0;
  15014. }
  15015. }
  15016. // Get editable root node normaly the body element but sometimes a div or span
  15017. editableRoot = getEditableRoot(container);
  15018. // If there is no editable root then enter is done inside a contentEditable false element
  15019. if (!editableRoot) {
  15020. return;
  15021. }
  15022. undoManager.beforeChange();
  15023. // If editable root isn't block nor the root of the editor
  15024. if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
  15025. if (!newBlockName || shiftKey) {
  15026. insertBr();
  15027. }
  15028. return;
  15029. }
  15030. // Wrap the current node and it's sibling in a default block if it's needed.
  15031. // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
  15032. // This won't happen if root blocks are disabled or the shiftKey is pressed
  15033. if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
  15034. container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
  15035. }
  15036. // Find parent block and setup empty block paddings
  15037. parentBlock = dom.getParent(container, dom.isBlock);
  15038. containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  15039. // Setup block names
  15040. parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  15041. containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  15042. // Enter inside block contained within a LI then split or insert before/after LI
  15043. if (containerBlockName == 'LI' && !evt.ctrlKey) {
  15044. parentBlock = containerBlock;
  15045. parentBlockName = containerBlockName;
  15046. }
  15047. // Handle enter in LI
  15048. if (parentBlockName == 'LI') {
  15049. if (!newBlockName && shiftKey) {
  15050. insertBr();
  15051. return;
  15052. }
  15053. // Handle enter inside an empty list item
  15054. if (dom.isEmpty(parentBlock)) {
  15055. handleEmptyListItem();
  15056. return;
  15057. }
  15058. }
  15059. // Don't split PRE tags but insert a BR instead easier when writing code samples etc
  15060. if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
  15061. if (!shiftKey) {
  15062. insertBr();
  15063. return;
  15064. }
  15065. } else {
  15066. // If no root block is configured then insert a BR by default or if the shiftKey is pressed
  15067. if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
  15068. insertBr();
  15069. return;
  15070. }
  15071. }
  15072. // If parent block is root then never insert new blocks
  15073. if (newBlockName && parentBlock === editor.getBody()) {
  15074. return;
  15075. }
  15076. // Default block name if it's not configured
  15077. newBlockName = newBlockName || 'P';
  15078. // Insert new block before/after the parent block depending on caret location
  15079. if (isCaretAtStartOrEndOfBlock()) {
  15080. // 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
  15081. if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
  15082. newBlock = createNewBlock(newBlockName);
  15083. } else {
  15084. newBlock = createNewBlock();
  15085. }
  15086. // Split the current container block element if enter is pressed inside an empty inner block element
  15087. if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
  15088. // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
  15089. newBlock = dom.split(containerBlock, parentBlock);
  15090. } else {
  15091. dom.insertAfter(newBlock, parentBlock);
  15092. }
  15093. moveToCaretPosition(newBlock);
  15094. } else if (isCaretAtStartOrEndOfBlock(true)) {
  15095. // Insert new block before
  15096. newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
  15097. renderBlockOnIE(newBlock);
  15098. moveToCaretPosition(parentBlock);
  15099. } else {
  15100. // Extract after fragment and insert it after the current block
  15101. tmpRng = rng.cloneRange();
  15102. tmpRng.setEndAfter(parentBlock);
  15103. fragment = tmpRng.extractContents();
  15104. trimLeadingLineBreaks(fragment);
  15105. newBlock = fragment.firstChild;
  15106. dom.insertAfter(fragment, parentBlock);
  15107. trimInlineElementsOnLeftSideOfBlock(newBlock);
  15108. addBrToBlockIfNeeded(parentBlock);
  15109. moveToCaretPosition(newBlock);
  15110. }
  15111. dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
  15112. // Allow custom handling of new blocks
  15113. editor.fire('NewBlock', { newBlock: newBlock });
  15114. undoManager.add();
  15115. }
  15116. editor.on('keydown', function(evt) {
  15117. if (evt.keyCode == 13) {
  15118. if (handleEnterKey(evt) !== false) {
  15119. evt.preventDefault();
  15120. }
  15121. }
  15122. });
  15123. };
  15124. });
  15125. // Included from: js/tinymce/classes/ForceBlocks.js
  15126. /**
  15127. * ForceBlocks.js
  15128. *
  15129. * Copyright, Moxiecode Systems AB
  15130. * Released under LGPL License.
  15131. *
  15132. * License: http://www.tinymce.com/license
  15133. * Contributing: http://www.tinymce.com/contributing
  15134. */
  15135. define("tinymce/ForceBlocks", [], function() {
  15136. return function(editor) {
  15137. var settings = editor.settings, dom = editor.dom, selection = editor.selection;
  15138. var schema = editor.schema, blockElements = schema.getBlockElements();
  15139. function addRootBlocks() {
  15140. var node = selection.getStart(), rootNode = editor.getBody(), rng;
  15141. var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
  15142. var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
  15143. var tmpRng, rootNodeName, forcedRootBlock;
  15144. forcedRootBlock = settings.forced_root_block;
  15145. if (!node || node.nodeType !== 1 || !forcedRootBlock) {
  15146. return;
  15147. }
  15148. // Check if node is wrapped in block
  15149. while (node && node != rootNode) {
  15150. if (blockElements[node.nodeName]) {
  15151. return;
  15152. }
  15153. node = node.parentNode;
  15154. }
  15155. // Get current selection
  15156. rng = selection.getRng();
  15157. if (rng.setStart) {
  15158. startContainer = rng.startContainer;
  15159. startOffset = rng.startOffset;
  15160. endContainer = rng.endContainer;
  15161. endOffset = rng.endOffset;
  15162. try {
  15163. restoreSelection = editor.getDoc().activeElement === rootNode;
  15164. } catch (ex) {
  15165. // IE throws unspecified error here sometimes
  15166. }
  15167. } else {
  15168. // Force control range into text range
  15169. if (rng.item) {
  15170. node = rng.item(0);
  15171. rng = editor.getDoc().body.createTextRange();
  15172. rng.moveToElementText(node);
  15173. }
  15174. restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
  15175. tmpRng = rng.duplicate();
  15176. tmpRng.collapse(true);
  15177. startOffset = tmpRng.move('character', offset) * -1;
  15178. if (!tmpRng.collapsed) {
  15179. tmpRng = rng.duplicate();
  15180. tmpRng.collapse(false);
  15181. endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
  15182. }
  15183. }
  15184. // Wrap non block elements and text nodes
  15185. node = rootNode.firstChild;
  15186. rootNodeName = rootNode.nodeName.toLowerCase();
  15187. while (node) {
  15188. // TODO: Break this up, too complex
  15189. if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
  15190. schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
  15191. // Remove empty text nodes
  15192. if (node.nodeType === 3 && node.nodeValue.length === 0) {
  15193. tempNode = node;
  15194. node = node.nextSibling;
  15195. dom.remove(tempNode);
  15196. continue;
  15197. }
  15198. if (!rootBlockNode) {
  15199. rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
  15200. node.parentNode.insertBefore(rootBlockNode, node);
  15201. wrapped = true;
  15202. }
  15203. tempNode = node;
  15204. node = node.nextSibling;
  15205. rootBlockNode.appendChild(tempNode);
  15206. } else {
  15207. rootBlockNode = null;
  15208. node = node.nextSibling;
  15209. }
  15210. }
  15211. if (wrapped && restoreSelection) {
  15212. if (rng.setStart) {
  15213. rng.setStart(startContainer, startOffset);
  15214. rng.setEnd(endContainer, endOffset);
  15215. selection.setRng(rng);
  15216. } else {
  15217. // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
  15218. try {
  15219. rng = editor.getDoc().body.createTextRange();
  15220. rng.moveToElementText(rootNode);
  15221. rng.collapse(true);
  15222. rng.moveStart('character', startOffset);
  15223. if (endOffset > 0) {
  15224. rng.moveEnd('character', endOffset);
  15225. }
  15226. rng.select();
  15227. } catch (ex) {
  15228. // Ignore
  15229. }
  15230. }
  15231. editor.nodeChanged();
  15232. }
  15233. }
  15234. // Force root blocks
  15235. if (settings.forced_root_block) {
  15236. editor.on('NodeChange', addRootBlocks);
  15237. }
  15238. };
  15239. });
  15240. // Included from: js/tinymce/classes/EditorCommands.js
  15241. /**
  15242. * EditorCommands.js
  15243. *
  15244. * Copyright, Moxiecode Systems AB
  15245. * Released under LGPL License.
  15246. *
  15247. * License: http://www.tinymce.com/license
  15248. * Contributing: http://www.tinymce.com/contributing
  15249. */
  15250. /**
  15251. * This class enables you to add custom editor commands and it contains
  15252. * overrides for native browser commands to address various bugs and issues.
  15253. *
  15254. * @class tinymce.EditorCommands
  15255. */
  15256. define("tinymce/EditorCommands", [
  15257. "tinymce/html/Serializer",
  15258. "tinymce/Env",
  15259. "tinymce/util/Tools"
  15260. ], function(Serializer, Env, Tools) {
  15261. // Added for compression purposes
  15262. var each = Tools.each, extend = Tools.extend;
  15263. var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
  15264. var isGecko = Env.gecko, isIE = Env.ie;
  15265. var TRUE = true, FALSE = false;
  15266. return function(editor) {
  15267. var dom = editor.dom,
  15268. selection = editor.selection,
  15269. commands = {state: {}, exec: {}, value: {}},
  15270. settings = editor.settings,
  15271. formatter = editor.formatter,
  15272. bookmark;
  15273. /**
  15274. * Executes the specified command.
  15275. *
  15276. * @method execCommand
  15277. * @param {String} command Command to execute.
  15278. * @param {Boolean} ui Optional user interface state.
  15279. * @param {Object} value Optional value for command.
  15280. * @return {Boolean} true/false if the command was found or not.
  15281. */
  15282. function execCommand(command, ui, value) {
  15283. var func;
  15284. command = command.toLowerCase();
  15285. if ((func = commands.exec[command])) {
  15286. func(command, ui, value);
  15287. return TRUE;
  15288. }
  15289. return FALSE;
  15290. }
  15291. /**
  15292. * Queries the current state for a command for example if the current selection is "bold".
  15293. *
  15294. * @method queryCommandState
  15295. * @param {String} command Command to check the state of.
  15296. * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
  15297. */
  15298. function queryCommandState(command) {
  15299. var func;
  15300. command = command.toLowerCase();
  15301. if ((func = commands.state[command])) {
  15302. return func(command);
  15303. }
  15304. return -1;
  15305. }
  15306. /**
  15307. * Queries the command value for example the current fontsize.
  15308. *
  15309. * @method queryCommandValue
  15310. * @param {String} command Command to check the value of.
  15311. * @return {Object} Command value of false if it's not found.
  15312. */
  15313. function queryCommandValue(command) {
  15314. var func;
  15315. command = command.toLowerCase();
  15316. if ((func = commands.value[command])) {
  15317. return func(command);
  15318. }
  15319. return FALSE;
  15320. }
  15321. /**
  15322. * Adds commands to the command collection.
  15323. *
  15324. * @method addCommands
  15325. * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
  15326. * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
  15327. */
  15328. function addCommands(command_list, type) {
  15329. type = type || 'exec';
  15330. each(command_list, function(callback, command) {
  15331. each(command.toLowerCase().split(','), function(command) {
  15332. commands[type][command] = callback;
  15333. });
  15334. });
  15335. }
  15336. // Expose public methods
  15337. extend(this, {
  15338. execCommand: execCommand,
  15339. queryCommandState: queryCommandState,
  15340. queryCommandValue: queryCommandValue,
  15341. addCommands: addCommands
  15342. });
  15343. // Private methods
  15344. function execNativeCommand(command, ui, value) {
  15345. if (ui === undefined) {
  15346. ui = FALSE;
  15347. }
  15348. if (value === undefined) {
  15349. value = null;
  15350. }
  15351. return editor.getDoc().execCommand(command, ui, value);
  15352. }
  15353. function isFormatMatch(name) {
  15354. return formatter.match(name);
  15355. }
  15356. function toggleFormat(name, value) {
  15357. formatter.toggle(name, value ? {value: value} : undefined);
  15358. editor.nodeChanged();
  15359. }
  15360. function storeSelection(type) {
  15361. bookmark = selection.getBookmark(type);
  15362. }
  15363. function restoreSelection() {
  15364. selection.moveToBookmark(bookmark);
  15365. }
  15366. // Add execCommand overrides
  15367. addCommands({
  15368. // Ignore these, added for compatibility
  15369. 'mceResetDesignMode,mceBeginUndoLevel': function() {},
  15370. // Add undo manager logic
  15371. 'mceEndUndoLevel,mceAddUndoLevel': function() {
  15372. editor.undoManager.add();
  15373. },
  15374. 'Cut,Copy,Paste': function(command) {
  15375. var doc = editor.getDoc(), failed;
  15376. // Try executing the native command
  15377. try {
  15378. execNativeCommand(command);
  15379. } catch (ex) {
  15380. // Command failed
  15381. failed = TRUE;
  15382. }
  15383. // Present alert message about clipboard access not being available
  15384. if (failed || !doc.queryCommandSupported(command)) {
  15385. var msg = editor.translate(
  15386. "Your browser doesn't support direct access to the clipboard. " +
  15387. "Please use the Ctrl+X/C/V keyboard shortcuts instead."
  15388. );
  15389. if (Env.mac) {
  15390. msg = msg.replace(/Ctrl\+/g, '\u2318+');
  15391. }
  15392. editor.windowManager.alert(msg);
  15393. }
  15394. },
  15395. // Override unlink command
  15396. unlink: function() {
  15397. if (selection.isCollapsed()) {
  15398. var elm = selection.getNode();
  15399. if (elm.tagName == 'A') {
  15400. editor.dom.remove(elm, true);
  15401. }
  15402. return;
  15403. }
  15404. formatter.remove("link");
  15405. },
  15406. // Override justify commands to use the text formatter engine
  15407. 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  15408. var align = command.substring(7);
  15409. if (align == 'full') {
  15410. align = 'justify';
  15411. }
  15412. // Remove all other alignments first
  15413. each('left,center,right,justify'.split(','), function(name) {
  15414. if (align != name) {
  15415. formatter.remove('align' + name);
  15416. }
  15417. });
  15418. toggleFormat('align' + align);
  15419. execCommand('mceRepaint');
  15420. },
  15421. // Override list commands to fix WebKit bug
  15422. 'InsertUnorderedList,InsertOrderedList': function(command) {
  15423. var listElm, listParent;
  15424. execNativeCommand(command);
  15425. // WebKit produces lists within block elements so we need to split them
  15426. // we will replace the native list creation logic to custom logic later on
  15427. // TODO: Remove this when the list creation logic is removed
  15428. listElm = dom.getParent(selection.getNode(), 'ol,ul');
  15429. if (listElm) {
  15430. listParent = listElm.parentNode;
  15431. // If list is within a text block then split that block
  15432. if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
  15433. storeSelection();
  15434. dom.split(listParent, listElm);
  15435. restoreSelection();
  15436. }
  15437. }
  15438. },
  15439. // Override commands to use the text formatter engine
  15440. 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  15441. toggleFormat(command);
  15442. },
  15443. // Override commands to use the text formatter engine
  15444. 'ForeColor,HiliteColor,FontName': function(command, ui, value) {
  15445. toggleFormat(command, value);
  15446. },
  15447. FontSize: function(command, ui, value) {
  15448. var fontClasses, fontSizes;
  15449. // Convert font size 1-7 to styles
  15450. if (value >= 1 && value <= 7) {
  15451. fontSizes = explode(settings.font_size_style_values);
  15452. fontClasses = explode(settings.font_size_classes);
  15453. if (fontClasses) {
  15454. value = fontClasses[value - 1] || value;
  15455. } else {
  15456. value = fontSizes[value - 1] || value;
  15457. }
  15458. }
  15459. toggleFormat(command, value);
  15460. },
  15461. RemoveFormat: function(command) {
  15462. formatter.remove(command);
  15463. },
  15464. mceBlockQuote: function() {
  15465. toggleFormat('blockquote');
  15466. },
  15467. FormatBlock: function(command, ui, value) {
  15468. return toggleFormat(value || 'p');
  15469. },
  15470. mceCleanup: function() {
  15471. var bookmark = selection.getBookmark();
  15472. editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
  15473. selection.moveToBookmark(bookmark);
  15474. },
  15475. mceRemoveNode: function(command, ui, value) {
  15476. var node = value || selection.getNode();
  15477. // Make sure that the body node isn't removed
  15478. if (node != editor.getBody()) {
  15479. storeSelection();
  15480. editor.dom.remove(node, TRUE);
  15481. restoreSelection();
  15482. }
  15483. },
  15484. mceSelectNodeDepth: function(command, ui, value) {
  15485. var counter = 0;
  15486. dom.getParent(selection.getNode(), function(node) {
  15487. if (node.nodeType == 1 && counter++ == value) {
  15488. selection.select(node);
  15489. return FALSE;
  15490. }
  15491. }, editor.getBody());
  15492. },
  15493. mceSelectNode: function(command, ui, value) {
  15494. selection.select(value);
  15495. },
  15496. mceInsertContent: function(command, ui, value) {
  15497. var parser, serializer, parentNode, rootNode, fragment, args;
  15498. var marker, rng, node, node2, bookmarkHtml;
  15499. function trimOrPaddLeftRight(html) {
  15500. var rng, container, offset;
  15501. rng = selection.getRng(true);
  15502. container = rng.startContainer;
  15503. offset = rng.startOffset;
  15504. function hasSiblingText(siblingName) {
  15505. return container[siblingName] && container[siblingName].nodeType == 3;
  15506. }
  15507. if (container.nodeType == 3) {
  15508. if (offset > 0) {
  15509. html = html.replace(/^&nbsp;/, ' ');
  15510. } else if (!hasSiblingText('previousSibling')) {
  15511. html = html.replace(/^ /, '&nbsp;');
  15512. }
  15513. if (offset < container.length) {
  15514. html = html.replace(/&nbsp;(<br>|)$/, ' ');
  15515. } else if (!hasSiblingText('nextSibling')) {
  15516. html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
  15517. }
  15518. }
  15519. return html;
  15520. }
  15521. // Check for whitespace before/after value
  15522. if (/^ | $/.test(value)) {
  15523. value = trimOrPaddLeftRight(value);
  15524. }
  15525. // Setup parser and serializer
  15526. parser = editor.parser;
  15527. serializer = new Serializer({}, editor.schema);
  15528. bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#200B;</span>';
  15529. // Run beforeSetContent handlers on the HTML to be inserted
  15530. args = {content: value, format: 'html', selection: true};
  15531. editor.fire('BeforeSetContent', args);
  15532. value = args.content;
  15533. // Add caret at end of contents if it's missing
  15534. if (value.indexOf('{$caret}') == -1) {
  15535. value += '{$caret}';
  15536. }
  15537. // Replace the caret marker with a span bookmark element
  15538. value = value.replace(/\{\$caret\}/, bookmarkHtml);
  15539. // If selection is at <body>|<p></p> then move it into <body><p>|</p>
  15540. rng = selection.getRng();
  15541. var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
  15542. var body = editor.getBody();
  15543. if (caretElement === body && selection.isCollapsed()) {
  15544. if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
  15545. rng = dom.createRng();
  15546. rng.setStart(body.firstChild, 0);
  15547. rng.setEnd(body.firstChild, 0);
  15548. selection.setRng(rng);
  15549. }
  15550. }
  15551. // Insert node maker where we will insert the new HTML and get it's parent
  15552. if (!selection.isCollapsed()) {
  15553. editor.getDoc().execCommand('Delete', false, null);
  15554. }
  15555. parentNode = selection.getNode();
  15556. // Parse the fragment within the context of the parent node
  15557. var parserArgs = {context: parentNode.nodeName.toLowerCase()};
  15558. fragment = parser.parse(value, parserArgs);
  15559. // Move the caret to a more suitable location
  15560. node = fragment.lastChild;
  15561. if (node.attr('id') == 'mce_marker') {
  15562. marker = node;
  15563. for (node = node.prev; node; node = node.walk(true)) {
  15564. if (node.type == 3 || !dom.isBlock(node.name)) {
  15565. node.parent.insert(marker, node, node.name === 'br');
  15566. break;
  15567. }
  15568. }
  15569. }
  15570. // If parser says valid we can insert the contents into that parent
  15571. if (!parserArgs.invalid) {
  15572. value = serializer.serialize(fragment);
  15573. // Check if parent is empty or only has one BR element then set the innerHTML of that parent
  15574. node = parentNode.firstChild;
  15575. node2 = parentNode.lastChild;
  15576. if (!node || (node === node2 && node.nodeName === 'BR')) {
  15577. dom.setHTML(parentNode, value);
  15578. } else {
  15579. selection.setContent(value);
  15580. }
  15581. } else {
  15582. // If the fragment was invalid within that context then we need
  15583. // to parse and process the parent it's inserted into
  15584. // Insert bookmark node and get the parent
  15585. selection.setContent(bookmarkHtml);
  15586. parentNode = selection.getNode();
  15587. rootNode = editor.getBody();
  15588. // Opera will return the document node when selection is in root
  15589. if (parentNode.nodeType == 9) {
  15590. parentNode = node = rootNode;
  15591. } else {
  15592. node = parentNode;
  15593. }
  15594. // Find the ancestor just before the root element
  15595. while (node !== rootNode) {
  15596. parentNode = node;
  15597. node = node.parentNode;
  15598. }
  15599. // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
  15600. value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
  15601. value = serializer.serialize(
  15602. parser.parse(
  15603. // Need to replace by using a function since $ in the contents would otherwise be a problem
  15604. value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
  15605. return serializer.serialize(fragment);
  15606. })
  15607. )
  15608. );
  15609. // Set the inner/outer HTML depending on if we are in the root or not
  15610. if (parentNode == rootNode) {
  15611. dom.setHTML(rootNode, value);
  15612. } else {
  15613. dom.setOuterHTML(parentNode, value);
  15614. }
  15615. }
  15616. marker = dom.get('mce_marker');
  15617. selection.scrollIntoView(marker);
  15618. // Move selection before marker and remove it
  15619. rng = dom.createRng();
  15620. // If previous sibling is a text node set the selection to the end of that node
  15621. node = marker.previousSibling;
  15622. if (node && node.nodeType == 3) {
  15623. rng.setStart(node, node.nodeValue.length);
  15624. // TODO: Why can't we normalize on IE
  15625. if (!isIE) {
  15626. node2 = marker.nextSibling;
  15627. if (node2 && node2.nodeType == 3) {
  15628. node.appendData(node2.data);
  15629. node2.parentNode.removeChild(node2);
  15630. }
  15631. }
  15632. } else {
  15633. // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
  15634. rng.setStartBefore(marker);
  15635. rng.setEndBefore(marker);
  15636. }
  15637. // Remove the marker node and set the new range
  15638. dom.remove(marker);
  15639. selection.setRng(rng);
  15640. // Dispatch after event and add any visual elements needed
  15641. editor.fire('SetContent', args);
  15642. editor.addVisual();
  15643. },
  15644. mceInsertRawHTML: function(command, ui, value) {
  15645. selection.setContent('tiny_mce_marker');
  15646. editor.setContent(
  15647. editor.getContent().replace(/tiny_mce_marker/g, function() {
  15648. return value;
  15649. })
  15650. );
  15651. },
  15652. mceToggleFormat: function(command, ui, value) {
  15653. toggleFormat(value);
  15654. },
  15655. mceSetContent: function(command, ui, value) {
  15656. editor.setContent(value);
  15657. },
  15658. 'Indent,Outdent': function(command) {
  15659. var intentValue, indentUnit, value;
  15660. // Setup indent level
  15661. intentValue = settings.indentation;
  15662. indentUnit = /[a-z%]+$/i.exec(intentValue);
  15663. intentValue = parseInt(intentValue, 10);
  15664. if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
  15665. // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
  15666. if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
  15667. formatter.apply('div');
  15668. }
  15669. each(selection.getSelectedBlocks(), function(element) {
  15670. if (element.nodeName != "LI") {
  15671. var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
  15672. indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
  15673. if (command == 'outdent') {
  15674. value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
  15675. dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
  15676. } else {
  15677. value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
  15678. dom.setStyle(element, indentStyleName, value);
  15679. }
  15680. }
  15681. });
  15682. } else {
  15683. execNativeCommand(command);
  15684. }
  15685. },
  15686. mceRepaint: function() {
  15687. if (isGecko) {
  15688. try {
  15689. storeSelection(TRUE);
  15690. if (selection.getSel()) {
  15691. selection.getSel().selectAllChildren(editor.getBody());
  15692. }
  15693. selection.collapse(TRUE);
  15694. restoreSelection();
  15695. } catch (ex) {
  15696. // Ignore
  15697. }
  15698. }
  15699. },
  15700. InsertHorizontalRule: function() {
  15701. editor.execCommand('mceInsertContent', false, '<hr />');
  15702. },
  15703. mceToggleVisualAid: function() {
  15704. editor.hasVisual = !editor.hasVisual;
  15705. editor.addVisual();
  15706. },
  15707. mceReplaceContent: function(command, ui, value) {
  15708. editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
  15709. },
  15710. mceInsertLink: function(command, ui, value) {
  15711. var anchor;
  15712. if (typeof(value) == 'string') {
  15713. value = {href: value};
  15714. }
  15715. anchor = dom.getParent(selection.getNode(), 'a');
  15716. // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
  15717. value.href = value.href.replace(' ', '%20');
  15718. // Remove existing links if there could be child links or that the href isn't specified
  15719. if (!anchor || !value.href) {
  15720. formatter.remove('link');
  15721. }
  15722. // Apply new link to selection
  15723. if (value.href) {
  15724. formatter.apply('link', value, anchor);
  15725. }
  15726. },
  15727. selectAll: function() {
  15728. var root = dom.getRoot(), rng;
  15729. if (selection.getRng().setStart) {
  15730. rng = dom.createRng();
  15731. rng.setStart(root, 0);
  15732. rng.setEnd(root, root.childNodes.length);
  15733. selection.setRng(rng);
  15734. } else {
  15735. // IE will render it's own root level block elements and sometimes
  15736. // even put font elements in them when the user starts typing. So we need to
  15737. // move the selection to a more suitable element from this:
  15738. // <body>|<p></p></body> to this: <body><p>|</p></body>
  15739. rng = selection.getRng();
  15740. if (!rng.item) {
  15741. rng.moveToElementText(root);
  15742. rng.select();
  15743. }
  15744. }
  15745. },
  15746. "delete": function() {
  15747. execNativeCommand("Delete");
  15748. // Check if body is empty after the delete call if so then set the contents
  15749. // to an empty string and move the caret to any block produced by that operation
  15750. // this fixes the issue with root blocks not being properly produced after a delete call on IE
  15751. var body = editor.getBody();
  15752. if (dom.isEmpty(body)) {
  15753. editor.setContent('');
  15754. if (body.firstChild && dom.isBlock(body.firstChild)) {
  15755. editor.selection.setCursorLocation(body.firstChild, 0);
  15756. } else {
  15757. editor.selection.setCursorLocation(body, 0);
  15758. }
  15759. }
  15760. },
  15761. mceNewDocument: function() {
  15762. editor.setContent('');
  15763. }
  15764. });
  15765. // Add queryCommandState overrides
  15766. addCommands({
  15767. // Override justify commands
  15768. 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  15769. var name = 'align' + command.substring(7);
  15770. var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
  15771. var matches = map(nodes, function(node) {
  15772. return !!formatter.matchNode(node, name);
  15773. });
  15774. return inArray(matches, TRUE) !== -1;
  15775. },
  15776. 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  15777. return isFormatMatch(command);
  15778. },
  15779. mceBlockQuote: function() {
  15780. return isFormatMatch('blockquote');
  15781. },
  15782. Outdent: function() {
  15783. var node;
  15784. if (settings.inline_styles) {
  15785. if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  15786. return TRUE;
  15787. }
  15788. if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  15789. return TRUE;
  15790. }
  15791. }
  15792. return (
  15793. queryCommandState('InsertUnorderedList') ||
  15794. queryCommandState('InsertOrderedList') ||
  15795. (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
  15796. );
  15797. },
  15798. 'InsertUnorderedList,InsertOrderedList': function(command) {
  15799. var list = dom.getParent(selection.getNode(), 'ul,ol');
  15800. return list &&
  15801. (
  15802. command === 'insertunorderedlist' && list.tagName === 'UL' ||
  15803. command === 'insertorderedlist' && list.tagName === 'OL'
  15804. );
  15805. }
  15806. }, 'state');
  15807. // Add queryCommandValue overrides
  15808. addCommands({
  15809. 'FontSize,FontName': function(command) {
  15810. var value = 0, parent;
  15811. if ((parent = dom.getParent(selection.getNode(), 'span'))) {
  15812. if (command == 'fontsize') {
  15813. value = parent.style.fontSize;
  15814. } else {
  15815. value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
  15816. }
  15817. }
  15818. return value;
  15819. }
  15820. }, 'value');
  15821. // Add undo manager logic
  15822. addCommands({
  15823. Undo: function() {
  15824. editor.undoManager.undo();
  15825. },
  15826. Redo: function() {
  15827. editor.undoManager.redo();
  15828. }
  15829. });
  15830. };
  15831. });
  15832. // Included from: js/tinymce/classes/util/URI.js
  15833. /**
  15834. * URI.js
  15835. *
  15836. * Copyright, Moxiecode Systems AB
  15837. * Released under LGPL License.
  15838. *
  15839. * License: http://www.tinymce.com/license
  15840. * Contributing: http://www.tinymce.com/contributing
  15841. */
  15842. /**
  15843. * This class handles parsing, modification and serialization of URI/URL strings.
  15844. * @class tinymce.util.URI
  15845. */
  15846. define("tinymce/util/URI", [
  15847. "tinymce/util/Tools"
  15848. ], function(Tools) {
  15849. var each = Tools.each, trim = Tools.trim,
  15850. DEFAULT_PORTS = {
  15851. 'ftp': 21,
  15852. 'http': 80,
  15853. 'https': 443,
  15854. 'mailto': 25
  15855. };
  15856. /**
  15857. * Constructs a new URI instance.
  15858. *
  15859. * @constructor
  15860. * @method URI
  15861. * @param {String} url URI string to parse.
  15862. * @param {Object} settings Optional settings object.
  15863. */
  15864. function URI(url, settings) {
  15865. var self = this, baseUri, base_url;
  15866. // Trim whitespace
  15867. url = trim(url);
  15868. // Default settings
  15869. settings = self.settings = settings || {};
  15870. // Strange app protocol that isn't http/https or local anchor
  15871. // For example: mailto,skype,tel etc.
  15872. if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
  15873. self.source = url;
  15874. return;
  15875. }
  15876. var isProtocolRelative = url.indexOf('//') === 0;
  15877. // Absolute path with no host, fake host and protocol
  15878. if (url.indexOf('/') === 0 && !isProtocolRelative) {
  15879. url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url;
  15880. }
  15881. // Relative path http:// or protocol relative //path
  15882. if (!/^[\w\-]*:?\/\//.test(url)) {
  15883. base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
  15884. if (settings.base_uri.protocol === "") {
  15885. url = '//mce_host' + self.toAbsPath(base_url, url);
  15886. } else {
  15887. url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url);
  15888. }
  15889. }
  15890. // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
  15891. url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
  15892. /*jshint maxlen: 255 */
  15893. /*eslint max-len: 0 */
  15894. url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
  15895. each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
  15896. var part = url[i];
  15897. // Zope 3 workaround, they use @@something
  15898. if (part) {
  15899. part = part.replace(/\(mce_at\)/g, '@@');
  15900. }
  15901. self[v] = part;
  15902. });
  15903. baseUri = settings.base_uri;
  15904. if (baseUri) {
  15905. if (!self.protocol) {
  15906. self.protocol = baseUri.protocol;
  15907. }
  15908. if (!self.userInfo) {
  15909. self.userInfo = baseUri.userInfo;
  15910. }
  15911. if (!self.port && self.host === 'mce_host') {
  15912. self.port = baseUri.port;
  15913. }
  15914. if (!self.host || self.host === 'mce_host') {
  15915. self.host = baseUri.host;
  15916. }
  15917. self.source = '';
  15918. }
  15919. if (isProtocolRelative) {
  15920. self.protocol = '';
  15921. }
  15922. //t.path = t.path || '/';
  15923. }
  15924. URI.prototype = {
  15925. /**
  15926. * Sets the internal path part of the URI.
  15927. *
  15928. * @method setPath
  15929. * @param {string} path Path string to set.
  15930. */
  15931. setPath: function(path) {
  15932. var self = this;
  15933. path = /^(.*?)\/?(\w+)?$/.exec(path);
  15934. // Update path parts
  15935. self.path = path[0];
  15936. self.directory = path[1];
  15937. self.file = path[2];
  15938. // Rebuild source
  15939. self.source = '';
  15940. self.getURI();
  15941. },
  15942. /**
  15943. * Converts the specified URI into a relative URI based on the current URI instance location.
  15944. *
  15945. * @method toRelative
  15946. * @param {String} uri URI to convert into a relative path/URI.
  15947. * @return {String} Relative URI from the point specified in the current URI instance.
  15948. * @example
  15949. * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
  15950. * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
  15951. */
  15952. toRelative: function(uri) {
  15953. var self = this, output;
  15954. if (uri === "./") {
  15955. return uri;
  15956. }
  15957. uri = new URI(uri, {base_uri: self});
  15958. // Not on same domain/port or protocol
  15959. if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
  15960. (self.protocol != uri.protocol && uri.protocol !== "")) {
  15961. return uri.getURI();
  15962. }
  15963. var tu = self.getURI(), uu = uri.getURI();
  15964. // Allow usage of the base_uri when relative_urls = true
  15965. if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
  15966. return tu;
  15967. }
  15968. output = self.toRelPath(self.path, uri.path);
  15969. // Add query
  15970. if (uri.query) {
  15971. output += '?' + uri.query;
  15972. }
  15973. // Add anchor
  15974. if (uri.anchor) {
  15975. output += '#' + uri.anchor;
  15976. }
  15977. return output;
  15978. },
  15979. /**
  15980. * Converts the specified URI into a absolute URI based on the current URI instance location.
  15981. *
  15982. * @method toAbsolute
  15983. * @param {String} uri URI to convert into a relative path/URI.
  15984. * @param {Boolean} noHost No host and protocol prefix.
  15985. * @return {String} Absolute URI from the point specified in the current URI instance.
  15986. * @example
  15987. * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
  15988. * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
  15989. */
  15990. toAbsolute: function(uri, noHost) {
  15991. uri = new URI(uri, {base_uri: this});
  15992. return uri.getURI(noHost && this.isSameOrigin(uri));
  15993. },
  15994. /**
  15995. * Determine whether the given URI has the same origin as this URI. Based on RFC-6454.
  15996. * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they
  15997. * won't match, if the port specifications differ.
  15998. *
  15999. * @method isSameOrigin
  16000. * @param {tinymce.util.URI} uri Uri instance to compare.
  16001. * @returns {Boolean} True if the origins are the same.
  16002. */
  16003. isSameOrigin: function(uri) {
  16004. if (this.host == uri.host && this.protocol == uri.protocol){
  16005. if (this.port == uri.port) {
  16006. return true;
  16007. }
  16008. var defaultPort = DEFAULT_PORTS[this.protocol];
  16009. if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
  16010. return true;
  16011. }
  16012. }
  16013. return false;
  16014. },
  16015. /**
  16016. * Converts a absolute path into a relative path.
  16017. *
  16018. * @method toRelPath
  16019. * @param {String} base Base point to convert the path from.
  16020. * @param {String} path Absolute path to convert into a relative path.
  16021. */
  16022. toRelPath: function(base, path) {
  16023. var items, breakPoint = 0, out = '', i, l;
  16024. // Split the paths
  16025. base = base.substring(0, base.lastIndexOf('/'));
  16026. base = base.split('/');
  16027. items = path.split('/');
  16028. if (base.length >= items.length) {
  16029. for (i = 0, l = base.length; i < l; i++) {
  16030. if (i >= items.length || base[i] != items[i]) {
  16031. breakPoint = i + 1;
  16032. break;
  16033. }
  16034. }
  16035. }
  16036. if (base.length < items.length) {
  16037. for (i = 0, l = items.length; i < l; i++) {
  16038. if (i >= base.length || base[i] != items[i]) {
  16039. breakPoint = i + 1;
  16040. break;
  16041. }
  16042. }
  16043. }
  16044. if (breakPoint === 1) {
  16045. return path;
  16046. }
  16047. for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
  16048. out += "../";
  16049. }
  16050. for (i = breakPoint - 1, l = items.length; i < l; i++) {
  16051. if (i != breakPoint - 1) {
  16052. out += "/" + items[i];
  16053. } else {
  16054. out += items[i];
  16055. }
  16056. }
  16057. return out;
  16058. },
  16059. /**
  16060. * Converts a relative path into a absolute path.
  16061. *
  16062. * @method toAbsPath
  16063. * @param {String} base Base point to convert the path from.
  16064. * @param {String} path Relative path to convert into an absolute path.
  16065. */
  16066. toAbsPath: function(base, path) {
  16067. var i, nb = 0, o = [], tr, outPath;
  16068. // Split paths
  16069. tr = /\/$/.test(path) ? '/' : '';
  16070. base = base.split('/');
  16071. path = path.split('/');
  16072. // Remove empty chunks
  16073. each(base, function(k) {
  16074. if (k) {
  16075. o.push(k);
  16076. }
  16077. });
  16078. base = o;
  16079. // Merge relURLParts chunks
  16080. for (i = path.length - 1, o = []; i >= 0; i--) {
  16081. // Ignore empty or .
  16082. if (path[i].length === 0 || path[i] === ".") {
  16083. continue;
  16084. }
  16085. // Is parent
  16086. if (path[i] === '..') {
  16087. nb++;
  16088. continue;
  16089. }
  16090. // Move up
  16091. if (nb > 0) {
  16092. nb--;
  16093. continue;
  16094. }
  16095. o.push(path[i]);
  16096. }
  16097. i = base.length - nb;
  16098. // If /a/b/c or /
  16099. if (i <= 0) {
  16100. outPath = o.reverse().join('/');
  16101. } else {
  16102. outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
  16103. }
  16104. // Add front / if it's needed
  16105. if (outPath.indexOf('/') !== 0) {
  16106. outPath = '/' + outPath;
  16107. }
  16108. // Add traling / if it's needed
  16109. if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
  16110. outPath += tr;
  16111. }
  16112. return outPath;
  16113. },
  16114. /**
  16115. * Returns the full URI of the internal structure.
  16116. *
  16117. * @method getURI
  16118. * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
  16119. */
  16120. getURI: function(noProtoHost) {
  16121. var s, self = this;
  16122. // Rebuild source
  16123. if (!self.source || noProtoHost) {
  16124. s = '';
  16125. if (!noProtoHost) {
  16126. if (self.protocol) {
  16127. s += self.protocol + '://';
  16128. } else {
  16129. s += '//';
  16130. }
  16131. if (self.userInfo) {
  16132. s += self.userInfo + '@';
  16133. }
  16134. if (self.host) {
  16135. s += self.host;
  16136. }
  16137. if (self.port) {
  16138. s += ':' + self.port;
  16139. }
  16140. }
  16141. if (self.path) {
  16142. s += self.path;
  16143. }
  16144. if (self.query) {
  16145. s += '?' + self.query;
  16146. }
  16147. if (self.anchor) {
  16148. s += '#' + self.anchor;
  16149. }
  16150. self.source = s;
  16151. }
  16152. return self.source;
  16153. }
  16154. };
  16155. return URI;
  16156. });
  16157. // Included from: js/tinymce/classes/util/Class.js
  16158. /**
  16159. * Class.js
  16160. *
  16161. * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
  16162. */
  16163. /**
  16164. * This utilitiy class is used for easier inheritage.
  16165. *
  16166. * Features:
  16167. * * Exposed super functions: this._super();
  16168. * * Mixins
  16169. * * Dummy functions
  16170. * * Property functions: var value = object.value(); and object.value(newValue);
  16171. * * Static functions
  16172. * * Defaults settings
  16173. */
  16174. define("tinymce/util/Class", [
  16175. "tinymce/util/Tools"
  16176. ], function(Tools) {
  16177. var each = Tools.each, extend = Tools.extend;
  16178. var extendClass, initializing;
  16179. function Class() {
  16180. }
  16181. // Provides classical inheritance, based on code made by John Resig
  16182. Class.extend = extendClass = function(prop) {
  16183. var self = this, _super = self.prototype, prototype, name, member;
  16184. // The dummy class constructor
  16185. function Class() {
  16186. var i, mixins, mixin, self = this;
  16187. // All construction is actually done in the init method
  16188. if (!initializing) {
  16189. // Run class constuctor
  16190. if (self.init) {
  16191. self.init.apply(self, arguments);
  16192. }
  16193. // Run mixin constructors
  16194. mixins = self.Mixins;
  16195. if (mixins) {
  16196. i = mixins.length;
  16197. while (i--) {
  16198. mixin = mixins[i];
  16199. if (mixin.init) {
  16200. mixin.init.apply(self, arguments);
  16201. }
  16202. }
  16203. }
  16204. }
  16205. }
  16206. // Dummy function, needs to be extended in order to provide functionality
  16207. function dummy() {
  16208. return this;
  16209. }
  16210. // Creates a overloaded method for the class
  16211. // this enables you to use this._super(); to call the super function
  16212. function createMethod(name, fn) {
  16213. return function(){
  16214. var self = this, tmp = self._super, ret;
  16215. self._super = _super[name];
  16216. ret = fn.apply(self, arguments);
  16217. self._super = tmp;
  16218. return ret;
  16219. };
  16220. }
  16221. // Instantiate a base class (but only create the instance,
  16222. // don't run the init constructor)
  16223. initializing = true;
  16224. /*eslint new-cap:0 */
  16225. prototype = new self();
  16226. initializing = false;
  16227. // Add mixins
  16228. if (prop.Mixins) {
  16229. each(prop.Mixins, function(mixin) {
  16230. mixin = mixin;
  16231. for (var name in mixin) {
  16232. if (name !== "init") {
  16233. prop[name] = mixin[name];
  16234. }
  16235. }
  16236. });
  16237. if (_super.Mixins) {
  16238. prop.Mixins = _super.Mixins.concat(prop.Mixins);
  16239. }
  16240. }
  16241. // Generate dummy methods
  16242. if (prop.Methods) {
  16243. each(prop.Methods.split(','), function(name) {
  16244. prop[name] = dummy;
  16245. });
  16246. }
  16247. // Generate property methods
  16248. if (prop.Properties) {
  16249. each(prop.Properties.split(','), function(name) {
  16250. var fieldName = '_' + name;
  16251. prop[name] = function(value) {
  16252. var self = this, undef;
  16253. // Set value
  16254. if (value !== undef) {
  16255. self[fieldName] = value;
  16256. return self;
  16257. }
  16258. // Get value
  16259. return self[fieldName];
  16260. };
  16261. });
  16262. }
  16263. // Static functions
  16264. if (prop.Statics) {
  16265. each(prop.Statics, function(func, name) {
  16266. Class[name] = func;
  16267. });
  16268. }
  16269. // Default settings
  16270. if (prop.Defaults && _super.Defaults) {
  16271. prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
  16272. }
  16273. // Copy the properties over onto the new prototype
  16274. for (name in prop) {
  16275. member = prop[name];
  16276. if (typeof member == "function" && _super[name]) {
  16277. prototype[name] = createMethod(name, member);
  16278. } else {
  16279. prototype[name] = member;
  16280. }
  16281. }
  16282. // Populate our constructed prototype object
  16283. Class.prototype = prototype;
  16284. // Enforce the constructor to be what we expect
  16285. Class.constructor = Class;
  16286. // And make this class extendible
  16287. Class.extend = extendClass;
  16288. return Class;
  16289. };
  16290. return Class;
  16291. });
  16292. // Included from: js/tinymce/classes/ui/Selector.js
  16293. /**
  16294. * Selector.js
  16295. *
  16296. * Copyright, Moxiecode Systems AB
  16297. * Released under LGPL License.
  16298. *
  16299. * License: http://www.tinymce.com/license
  16300. * Contributing: http://www.tinymce.com/contributing
  16301. */
  16302. /*eslint no-nested-ternary:0 */
  16303. /**
  16304. * Selector engine, enables you to select controls by using CSS like expressions.
  16305. * We currently only support basic CSS expressions to reduce the size of the core
  16306. * and the ones we support should be enough for most cases.
  16307. *
  16308. * @example
  16309. * Supported expressions:
  16310. * element
  16311. * element#name
  16312. * element.class
  16313. * element[attr]
  16314. * element[attr*=value]
  16315. * element[attr~=value]
  16316. * element[attr!=value]
  16317. * element[attr^=value]
  16318. * element[attr$=value]
  16319. * element:<state>
  16320. * element:not(<expression>)
  16321. * element:first
  16322. * element:last
  16323. * element:odd
  16324. * element:even
  16325. * element element
  16326. * element > element
  16327. *
  16328. * @class tinymce.ui.Selector
  16329. */
  16330. define("tinymce/ui/Selector", [
  16331. "tinymce/util/Class"
  16332. ], function(Class) {
  16333. "use strict";
  16334. /**
  16335. * Produces an array with a unique set of objects. It will not compare the values
  16336. * but the references of the objects.
  16337. *
  16338. * @private
  16339. * @method unqiue
  16340. * @param {Array} array Array to make into an array with unique items.
  16341. * @return {Array} Array with unique items.
  16342. */
  16343. function unique(array) {
  16344. var uniqueItems = [], i = array.length, item;
  16345. while (i--) {
  16346. item = array[i];
  16347. if (!item.__checked) {
  16348. uniqueItems.push(item);
  16349. item.__checked = 1;
  16350. }
  16351. }
  16352. i = uniqueItems.length;
  16353. while (i--) {
  16354. delete uniqueItems[i].__checked;
  16355. }
  16356. return uniqueItems;
  16357. }
  16358. var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
  16359. /*jshint maxlen:255 */
  16360. /*eslint max-len:0 */
  16361. var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
  16362. whiteSpace = /^\s*|\s*$/g,
  16363. Collection;
  16364. var Selector = Class.extend({
  16365. /**
  16366. * Constructs a new Selector instance.
  16367. *
  16368. * @constructor
  16369. * @method init
  16370. * @param {String} selector CSS like selector expression.
  16371. */
  16372. init: function(selector) {
  16373. var match = this.match;
  16374. function compileNameFilter(name) {
  16375. if (name) {
  16376. name = name.toLowerCase();
  16377. return function(item) {
  16378. return name === '*' || item.type === name;
  16379. };
  16380. }
  16381. }
  16382. function compileIdFilter(id) {
  16383. if (id) {
  16384. return function(item) {
  16385. return item._name === id;
  16386. };
  16387. }
  16388. }
  16389. function compileClassesFilter(classes) {
  16390. if (classes) {
  16391. classes = classes.split('.');
  16392. return function(item) {
  16393. var i = classes.length;
  16394. while (i--) {
  16395. if (!item.hasClass(classes[i])) {
  16396. return false;
  16397. }
  16398. }
  16399. return true;
  16400. };
  16401. }
  16402. }
  16403. function compileAttrFilter(name, cmp, check) {
  16404. if (name) {
  16405. return function(item) {
  16406. var value = item[name] ? item[name]() : '';
  16407. return !cmp ? !!check :
  16408. cmp === "=" ? value === check :
  16409. cmp === "*=" ? value.indexOf(check) >= 0 :
  16410. cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
  16411. cmp === "!=" ? value != check :
  16412. cmp === "^=" ? value.indexOf(check) === 0 :
  16413. cmp === "$=" ? value.substr(value.length - check.length) === check :
  16414. false;
  16415. };
  16416. }
  16417. }
  16418. function compilePsuedoFilter(name) {
  16419. var notSelectors;
  16420. if (name) {
  16421. name = /(?:not\((.+)\))|(.+)/i.exec(name);
  16422. if (!name[1]) {
  16423. name = name[2];
  16424. return function(item, index, length) {
  16425. return name === 'first' ? index === 0 :
  16426. name === 'last' ? index === length - 1 :
  16427. name === 'even' ? index % 2 === 0 :
  16428. name === 'odd' ? index % 2 === 1 :
  16429. item[name] ? item[name]() :
  16430. false;
  16431. };
  16432. } else {
  16433. // Compile not expression
  16434. notSelectors = parseChunks(name[1], []);
  16435. return function(item) {
  16436. return !match(item, notSelectors);
  16437. };
  16438. }
  16439. }
  16440. }
  16441. function compile(selector, filters, direct) {
  16442. var parts;
  16443. function add(filter) {
  16444. if (filter) {
  16445. filters.push(filter);
  16446. }
  16447. }
  16448. // Parse expression into parts
  16449. parts = expression.exec(selector.replace(whiteSpace, ''));
  16450. add(compileNameFilter(parts[1]));
  16451. add(compileIdFilter(parts[2]));
  16452. add(compileClassesFilter(parts[3]));
  16453. add(compileAttrFilter(parts[4], parts[5], parts[6]));
  16454. add(compilePsuedoFilter(parts[7]));
  16455. // Mark the filter with psuedo for performance
  16456. filters.psuedo = !!parts[7];
  16457. filters.direct = direct;
  16458. return filters;
  16459. }
  16460. // Parser logic based on Sizzle by John Resig
  16461. function parseChunks(selector, selectors) {
  16462. var parts = [], extra, matches, i;
  16463. do {
  16464. chunker.exec("");
  16465. matches = chunker.exec(selector);
  16466. if (matches) {
  16467. selector = matches[3];
  16468. parts.push(matches[1]);
  16469. if (matches[2]) {
  16470. extra = matches[3];
  16471. break;
  16472. }
  16473. }
  16474. } while (matches);
  16475. if (extra) {
  16476. parseChunks(extra, selectors);
  16477. }
  16478. selector = [];
  16479. for (i = 0; i < parts.length; i++) {
  16480. if (parts[i] != '>') {
  16481. selector.push(compile(parts[i], [], parts[i - 1] === '>'));
  16482. }
  16483. }
  16484. selectors.push(selector);
  16485. return selectors;
  16486. }
  16487. this._selectors = parseChunks(selector, []);
  16488. },
  16489. /**
  16490. * Returns true/false if the selector matches the specified control.
  16491. *
  16492. * @method match
  16493. * @param {tinymce.ui.Control} control Control to match agains the selector.
  16494. * @param {Array} selectors Optional array of selectors, mostly used internally.
  16495. * @return {Boolean} true/false state if the control matches or not.
  16496. */
  16497. match: function(control, selectors) {
  16498. var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
  16499. selectors = selectors || this._selectors;
  16500. for (i = 0, l = selectors.length; i < l; i++) {
  16501. selector = selectors[i];
  16502. sl = selector.length;
  16503. item = control;
  16504. count = 0;
  16505. for (si = sl - 1; si >= 0; si--) {
  16506. filters = selector[si];
  16507. while (item) {
  16508. // Find the index and length since a psuedo filter like :first needs it
  16509. if (filters.psuedo) {
  16510. siblings = item.parent().items();
  16511. index = length = siblings.length;
  16512. while (index--) {
  16513. if (siblings[index] === item) {
  16514. break;
  16515. }
  16516. }
  16517. }
  16518. for (fi = 0, fl = filters.length; fi < fl; fi++) {
  16519. if (!filters[fi](item, index, length)) {
  16520. fi = fl + 1;
  16521. break;
  16522. }
  16523. }
  16524. if (fi === fl) {
  16525. count++;
  16526. break;
  16527. } else {
  16528. // If it didn't match the right most expression then
  16529. // break since it's no point looking at the parents
  16530. if (si === sl - 1) {
  16531. break;
  16532. }
  16533. }
  16534. item = item.parent();
  16535. }
  16536. }
  16537. // If we found all selectors then return true otherwise continue looking
  16538. if (count === sl) {
  16539. return true;
  16540. }
  16541. }
  16542. return false;
  16543. },
  16544. /**
  16545. * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
  16546. *
  16547. * @method find
  16548. * @param {tinymce.ui.Control} container Container to look for items in.
  16549. * @return {tinymce.ui.Collection} Collection with matched elements.
  16550. */
  16551. find: function(container) {
  16552. var matches = [], i, l, selectors = this._selectors;
  16553. function collect(items, selector, index) {
  16554. var i, l, fi, fl, item, filters = selector[index];
  16555. for (i = 0, l = items.length; i < l; i++) {
  16556. item = items[i];
  16557. // Run each filter agains the item
  16558. for (fi = 0, fl = filters.length; fi < fl; fi++) {
  16559. if (!filters[fi](item, i, l)) {
  16560. fi = fl + 1;
  16561. break;
  16562. }
  16563. }
  16564. // All filters matched the item
  16565. if (fi === fl) {
  16566. // Matched item is on the last expression like: panel toolbar [button]
  16567. if (index == selector.length - 1) {
  16568. matches.push(item);
  16569. } else {
  16570. // Collect next expression type
  16571. if (item.items) {
  16572. collect(item.items(), selector, index + 1);
  16573. }
  16574. }
  16575. } else if (filters.direct) {
  16576. return;
  16577. }
  16578. // Collect child items
  16579. if (item.items) {
  16580. collect(item.items(), selector, index);
  16581. }
  16582. }
  16583. }
  16584. if (container.items) {
  16585. for (i = 0, l = selectors.length; i < l; i++) {
  16586. collect(container.items(), selectors[i], 0);
  16587. }
  16588. // Unique the matches if needed
  16589. if (l > 1) {
  16590. matches = unique(matches);
  16591. }
  16592. }
  16593. // Fix for circular reference
  16594. if (!Collection) {
  16595. // TODO: Fix me!
  16596. Collection = Selector.Collection;
  16597. }
  16598. return new Collection(matches);
  16599. }
  16600. });
  16601. return Selector;
  16602. });
  16603. // Included from: js/tinymce/classes/ui/Collection.js
  16604. /**
  16605. * Collection.js
  16606. *
  16607. * Copyright, Moxiecode Systems AB
  16608. * Released under LGPL License.
  16609. *
  16610. * License: http://www.tinymce.com/license
  16611. * Contributing: http://www.tinymce.com/contributing
  16612. */
  16613. /**
  16614. * Control collection, this class contains control instances and it enables you to
  16615. * perform actions on all the contained items. This is very similar to how jQuery works.
  16616. *
  16617. * @example
  16618. * someCollection.show().disabled(true);
  16619. *
  16620. * @class tinymce.ui.Collection
  16621. */
  16622. define("tinymce/ui/Collection", [
  16623. "tinymce/util/Tools",
  16624. "tinymce/ui/Selector",
  16625. "tinymce/util/Class"
  16626. ], function(Tools, Selector, Class) {
  16627. "use strict";
  16628. var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
  16629. proto = {
  16630. /**
  16631. * Current number of contained control instances.
  16632. *
  16633. * @field length
  16634. * @type Number
  16635. */
  16636. length: 0,
  16637. /**
  16638. * Constructor for the collection.
  16639. *
  16640. * @constructor
  16641. * @method init
  16642. * @param {Array} items Optional array with items to add.
  16643. */
  16644. init: function(items) {
  16645. if (items) {
  16646. this.add(items);
  16647. }
  16648. },
  16649. /**
  16650. * Adds new items to the control collection.
  16651. *
  16652. * @method add
  16653. * @param {Array} items Array if items to add to collection.
  16654. * @return {tinymce.ui.Collection} Current collection instance.
  16655. */
  16656. add: function(items) {
  16657. var self = this;
  16658. // Force single item into array
  16659. if (!Tools.isArray(items)) {
  16660. if (items instanceof Collection) {
  16661. self.add(items.toArray());
  16662. } else {
  16663. push.call(self, items);
  16664. }
  16665. } else {
  16666. push.apply(self, items);
  16667. }
  16668. return self;
  16669. },
  16670. /**
  16671. * Sets the contents of the collection. This will remove any existing items
  16672. * and replace them with the ones specified in the input array.
  16673. *
  16674. * @method set
  16675. * @param {Array} items Array with items to set into the Collection.
  16676. * @return {tinymce.ui.Collection} Collection instance.
  16677. */
  16678. set: function(items) {
  16679. var self = this, len = self.length, i;
  16680. self.length = 0;
  16681. self.add(items);
  16682. // Remove old entries
  16683. for (i = self.length; i < len; i++) {
  16684. delete self[i];
  16685. }
  16686. return self;
  16687. },
  16688. /**
  16689. * Filters the collection item based on the specified selector expression or selector function.
  16690. *
  16691. * @method filter
  16692. * @param {String} selector Selector expression to filter items by.
  16693. * @return {tinymce.ui.Collection} Collection containing the filtered items.
  16694. */
  16695. filter: function(selector) {
  16696. var self = this, i, l, matches = [], item, match;
  16697. // Compile string into selector expression
  16698. if (typeof(selector) === "string") {
  16699. selector = new Selector(selector);
  16700. match = function(item) {
  16701. return selector.match(item);
  16702. };
  16703. } else {
  16704. // Use selector as matching function
  16705. match = selector;
  16706. }
  16707. for (i = 0, l = self.length; i < l; i++) {
  16708. item = self[i];
  16709. if (match(item)) {
  16710. matches.push(item);
  16711. }
  16712. }
  16713. return new Collection(matches);
  16714. },
  16715. /**
  16716. * Slices the items within the collection.
  16717. *
  16718. * @method slice
  16719. * @param {Number} index Index to slice at.
  16720. * @param {Number} len Optional length to slice.
  16721. * @return {tinymce.ui.Collection} Current collection.
  16722. */
  16723. slice: function() {
  16724. return new Collection(slice.apply(this, arguments));
  16725. },
  16726. /**
  16727. * Makes the current collection equal to the specified index.
  16728. *
  16729. * @method eq
  16730. * @param {Number} index Index of the item to set the collection to.
  16731. * @return {tinymce.ui.Collection} Current collection.
  16732. */
  16733. eq: function(index) {
  16734. return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  16735. },
  16736. /**
  16737. * Executes the specified callback on each item in collection.
  16738. *
  16739. * @method each
  16740. * @param {function} callback Callback to execute for each item in collection.
  16741. * @return {tinymce.ui.Collection} Current collection instance.
  16742. */
  16743. each: function(callback) {
  16744. Tools.each(this, callback);
  16745. return this;
  16746. },
  16747. /**
  16748. * Returns an JavaScript array object of the contents inside the collection.
  16749. *
  16750. * @method toArray
  16751. * @return {Array} Array with all items from collection.
  16752. */
  16753. toArray: function() {
  16754. return Tools.toArray(this);
  16755. },
  16756. /**
  16757. * Finds the index of the specified control or return -1 if it isn't in the collection.
  16758. *
  16759. * @method indexOf
  16760. * @param {Control} ctrl Control instance to look for.
  16761. * @return {Number} Index of the specified control or -1.
  16762. */
  16763. indexOf: function(ctrl) {
  16764. var self = this, i = self.length;
  16765. while (i--) {
  16766. if (self[i] === ctrl) {
  16767. break;
  16768. }
  16769. }
  16770. return i;
  16771. },
  16772. /**
  16773. * Returns a new collection of the contents in reverse order.
  16774. *
  16775. * @method reverse
  16776. * @return {tinymce.ui.Collection} Collection instance with reversed items.
  16777. */
  16778. reverse: function() {
  16779. return new Collection(Tools.toArray(this).reverse());
  16780. },
  16781. /**
  16782. * Returns true/false if the class exists or not.
  16783. *
  16784. * @method hasClass
  16785. * @param {String} cls Class to check for.
  16786. * @return {Boolean} true/false state if the class exists or not.
  16787. */
  16788. hasClass: function(cls) {
  16789. return this[0] ? this[0].hasClass(cls) : false;
  16790. },
  16791. /**
  16792. * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
  16793. *
  16794. * @method prop
  16795. * @param {String} name Property name to get/set.
  16796. * @param {Object} value Optional object value to set.
  16797. * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
  16798. */
  16799. prop: function(name, value) {
  16800. var self = this, undef, item;
  16801. if (value !== undef) {
  16802. self.each(function(item) {
  16803. if (item[name]) {
  16804. item[name](value);
  16805. }
  16806. });
  16807. return self;
  16808. }
  16809. item = self[0];
  16810. if (item && item[name]) {
  16811. return item[name]();
  16812. }
  16813. },
  16814. /**
  16815. * Executes the specific function name with optional arguments an all items in collection if it exists.
  16816. *
  16817. * @example collection.exec("myMethod", arg1, arg2, arg3);
  16818. * @method exec
  16819. * @param {String} name Name of the function to execute.
  16820. * @param {Object} ... Multiple arguments to pass to each function.
  16821. * @return {tinymce.ui.Collection} Current collection.
  16822. */
  16823. exec: function(name) {
  16824. var self = this, args = Tools.toArray(arguments).slice(1);
  16825. self.each(function(item) {
  16826. if (item[name]) {
  16827. item[name].apply(item, args);
  16828. }
  16829. });
  16830. return self;
  16831. },
  16832. /**
  16833. * Remove all items from collection and DOM.
  16834. *
  16835. * @method remove
  16836. * @return {tinymce.ui.Collection} Current collection.
  16837. */
  16838. remove: function() {
  16839. var i = this.length;
  16840. while (i--) {
  16841. this[i].remove();
  16842. }
  16843. return this;
  16844. }
  16845. /**
  16846. * Fires the specified event by name and arguments on the control. This will execute all
  16847. * bound event handlers.
  16848. *
  16849. * @method fire
  16850. * @param {String} name Name of the event to fire.
  16851. * @param {Object} args Optional arguments to pass to the event.
  16852. * @return {tinymce.ui.Collection} Current collection instance.
  16853. */
  16854. // fire: function(event, args) {}, -- Generated by code below
  16855. /**
  16856. * Binds a callback to the specified event. This event can both be
  16857. * native browser events like "click" or custom ones like PostRender.
  16858. *
  16859. * The callback function will have two parameters the first one being the control that received the event
  16860. * the second one will be the event object either the browsers native event object or a custom JS object.
  16861. *
  16862. * @method on
  16863. * @param {String} name Name of the event to bind. For example "click".
  16864. * @param {String/function} callback Callback function to execute ones the event occurs.
  16865. * @return {tinymce.ui.Collection} Current collection instance.
  16866. */
  16867. // on: function(name, callback) {}, -- Generated by code below
  16868. /**
  16869. * Unbinds the specified event and optionally a specific callback. If you omit the name
  16870. * parameter all event handlers will be removed. If you omit the callback all event handles
  16871. * by the specified name will be removed.
  16872. *
  16873. * @method off
  16874. * @param {String} name Optional name for the event to unbind.
  16875. * @param {function} callback Optional callback function to unbind.
  16876. * @return {tinymce.ui.Collection} Current collection instance.
  16877. */
  16878. // off: function(name, callback) {}, -- Generated by code below
  16879. /**
  16880. * Shows the items in the current collection.
  16881. *
  16882. * @method show
  16883. * @return {tinymce.ui.Collection} Current collection instance.
  16884. */
  16885. // show: function() {}, -- Generated by code below
  16886. /**
  16887. * Hides the items in the current collection.
  16888. *
  16889. * @method hide
  16890. * @return {tinymce.ui.Collection} Current collection instance.
  16891. */
  16892. // hide: function() {}, -- Generated by code below
  16893. /**
  16894. * Sets/gets the text contents of the items in the current collection.
  16895. *
  16896. * @method text
  16897. * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
  16898. */
  16899. // text: function(value) {}, -- Generated by code below
  16900. /**
  16901. * Sets/gets the name contents of the items in the current collection.
  16902. *
  16903. * @method name
  16904. * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
  16905. */
  16906. // name: function(value) {}, -- Generated by code below
  16907. /**
  16908. * Sets/gets the disabled state on the items in the current collection.
  16909. *
  16910. * @method disabled
  16911. * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
  16912. */
  16913. // disabled: function(state) {}, -- Generated by code below
  16914. /**
  16915. * Sets/gets the active state on the items in the current collection.
  16916. *
  16917. * @method active
  16918. * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
  16919. */
  16920. // active: function(state) {}, -- Generated by code below
  16921. /**
  16922. * Sets/gets the selected state on the items in the current collection.
  16923. *
  16924. * @method selected
  16925. * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
  16926. */
  16927. // selected: function(state) {}, -- Generated by code below
  16928. /**
  16929. * Sets/gets the selected state on the items in the current collection.
  16930. *
  16931. * @method visible
  16932. * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
  16933. */
  16934. // visible: function(state) {}, -- Generated by code below
  16935. /**
  16936. * Adds a class to all items in the collection.
  16937. *
  16938. * @method addClass
  16939. * @param {String} cls Class to add to each item.
  16940. * @return {tinymce.ui.Collection} Current collection instance.
  16941. */
  16942. // addClass: function(cls) {}, -- Generated by code below
  16943. /**
  16944. * Removes the specified class from all items in collection.
  16945. *
  16946. * @method removeClass
  16947. * @param {String} cls Class to remove from each item.
  16948. * @return {tinymce.ui.Collection} Current collection instance.
  16949. */
  16950. // removeClass: function(cls) {}, -- Generated by code below
  16951. };
  16952. // Extend tinymce.ui.Collection prototype with some generated control specific methods
  16953. Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
  16954. proto[name] = function() {
  16955. var args = Tools.toArray(arguments);
  16956. this.each(function(ctrl) {
  16957. if (name in ctrl) {
  16958. ctrl[name].apply(ctrl, args);
  16959. }
  16960. });
  16961. return this;
  16962. };
  16963. });
  16964. // Extend tinymce.ui.Collection prototype with some property methods
  16965. Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
  16966. proto[name] = function(value) {
  16967. return this.prop(name, value);
  16968. };
  16969. });
  16970. // Create class based on the new prototype
  16971. Collection = Class.extend(proto);
  16972. // Stick Collection into Selector to prevent circual references
  16973. Selector.Collection = Collection;
  16974. return Collection;
  16975. });
  16976. // Included from: js/tinymce/classes/ui/DomUtils.js
  16977. /**
  16978. * DOMUtils.js
  16979. *
  16980. * Copyright, Moxiecode Systems AB
  16981. * Released under LGPL License.
  16982. *
  16983. * License: http://www.tinymce.com/license
  16984. * Contributing: http://www.tinymce.com/contributing
  16985. */
  16986. define("tinymce/ui/DomUtils", [
  16987. "tinymce/util/Tools",
  16988. "tinymce/dom/DOMUtils"
  16989. ], function(Tools, DOMUtils) {
  16990. "use strict";
  16991. return {
  16992. id: function() {
  16993. return DOMUtils.DOM.uniqueId();
  16994. },
  16995. createFragment: function(html) {
  16996. return DOMUtils.DOM.createFragment(html);
  16997. },
  16998. getWindowSize: function() {
  16999. return DOMUtils.DOM.getViewPort();
  17000. },
  17001. getSize: function(elm) {
  17002. var width, height;
  17003. if (elm.getBoundingClientRect) {
  17004. var rect = elm.getBoundingClientRect();
  17005. width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
  17006. height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
  17007. } else {
  17008. width = elm.offsetWidth;
  17009. height = elm.offsetHeight;
  17010. }
  17011. return {width: width, height: height};
  17012. },
  17013. getPos: function(elm, root) {
  17014. return DOMUtils.DOM.getPos(elm, root);
  17015. },
  17016. getViewPort: function(win) {
  17017. return DOMUtils.DOM.getViewPort(win);
  17018. },
  17019. get: function(id) {
  17020. return document.getElementById(id);
  17021. },
  17022. addClass : function(elm, cls) {
  17023. return DOMUtils.DOM.addClass(elm, cls);
  17024. },
  17025. removeClass : function(elm, cls) {
  17026. return DOMUtils.DOM.removeClass(elm, cls);
  17027. },
  17028. hasClass : function(elm, cls) {
  17029. return DOMUtils.DOM.hasClass(elm, cls);
  17030. },
  17031. toggleClass: function(elm, cls, state) {
  17032. return DOMUtils.DOM.toggleClass(elm, cls, state);
  17033. },
  17034. css: function(elm, name, value) {
  17035. return DOMUtils.DOM.setStyle(elm, name, value);
  17036. },
  17037. on: function(target, name, callback, scope) {
  17038. return DOMUtils.DOM.bind(target, name, callback, scope);
  17039. },
  17040. off: function(target, name, callback) {
  17041. return DOMUtils.DOM.unbind(target, name, callback);
  17042. },
  17043. fire: function(target, name, args) {
  17044. return DOMUtils.DOM.fire(target, name, args);
  17045. },
  17046. innerHtml: function(elm, html) {
  17047. // Workaround for <div> in <p> bug on IE 8 #6178
  17048. DOMUtils.DOM.setHTML(elm, html);
  17049. }
  17050. };
  17051. });
  17052. // Included from: js/tinymce/classes/ui/Control.js
  17053. /**
  17054. * Control.js
  17055. *
  17056. * Copyright, Moxiecode Systems AB
  17057. * Released under LGPL License.
  17058. *
  17059. * License: http://www.tinymce.com/license
  17060. * Contributing: http://www.tinymce.com/contributing
  17061. */
  17062. /*eslint consistent-this:0 */
  17063. /**
  17064. * This is the base class for all controls and containers. All UI control instances inherit
  17065. * from this one as it has the base logic needed by all of them.
  17066. *
  17067. * @class tinymce.ui.Control
  17068. */
  17069. define("tinymce/ui/Control", [
  17070. "tinymce/util/Class",
  17071. "tinymce/util/Tools",
  17072. "tinymce/ui/Collection",
  17073. "tinymce/ui/DomUtils"
  17074. ], function(Class, Tools, Collection, DomUtils) {
  17075. "use strict";
  17076. var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
  17077. " mouseout mouseenter mouseleave wheel keydown keypress input keyup contextmenu", " ");
  17078. var elementIdCache = {};
  17079. var hasMouseWheelEventSupport = "onmousewheel" in document;
  17080. var hasWheelEventSupport = false;
  17081. var classPrefix = "mce-";
  17082. var Control = Class.extend({
  17083. Statics: {
  17084. elementIdCache: elementIdCache,
  17085. classPrefix: classPrefix
  17086. },
  17087. isRtl: function() {
  17088. return Control.rtl;
  17089. },
  17090. /**
  17091. * Class/id prefix to use for all controls.
  17092. *
  17093. * @final
  17094. * @field {String} classPrefix
  17095. */
  17096. classPrefix: classPrefix,
  17097. /**
  17098. * Constructs a new control instance with the specified settings.
  17099. *
  17100. * @constructor
  17101. * @param {Object} settings Name/value object with settings.
  17102. * @setting {String} style Style CSS properties to add.
  17103. * @setting {String} border Border box values example: 1 1 1 1
  17104. * @setting {String} padding Padding box values example: 1 1 1 1
  17105. * @setting {String} margin Margin box values example: 1 1 1 1
  17106. * @setting {Number} minWidth Minimal width for the control.
  17107. * @setting {Number} minHeight Minimal height for the control.
  17108. * @setting {String} classes Space separated list of classes to add.
  17109. * @setting {String} role WAI-ARIA role to use for control.
  17110. * @setting {Boolean} hidden Is the control hidden by default.
  17111. * @setting {Boolean} disabled Is the control disabled by default.
  17112. * @setting {String} name Name of the control instance.
  17113. */
  17114. init: function(settings) {
  17115. var self = this, classes, i;
  17116. self.settings = settings = Tools.extend({}, self.Defaults, settings);
  17117. // Initial states
  17118. self._id = settings.id || DomUtils.id();
  17119. self._text = self._name = '';
  17120. self._width = self._height = 0;
  17121. self._aria = {role: settings.role};
  17122. // Setup classes
  17123. classes = settings.classes;
  17124. if (classes) {
  17125. classes = classes.split(' ');
  17126. classes.map = {};
  17127. i = classes.length;
  17128. while (i--) {
  17129. classes.map[classes[i]] = true;
  17130. }
  17131. }
  17132. self._classes = classes || [];
  17133. self.visible(true);
  17134. // Set some properties
  17135. Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
  17136. var value = settings[name], undef;
  17137. if (value !== undef) {
  17138. self[name](value);
  17139. } else if (self['_' + name] === undef) {
  17140. self['_' + name] = false;
  17141. }
  17142. });
  17143. self.on('click', function() {
  17144. if (self.disabled()) {
  17145. return false;
  17146. }
  17147. });
  17148. // TODO: Is this needed duplicate code see above?
  17149. if (settings.classes) {
  17150. Tools.each(settings.classes.split(' '), function(cls) {
  17151. self.addClass(cls);
  17152. });
  17153. }
  17154. /**
  17155. * Name/value object with settings for the current control.
  17156. *
  17157. * @field {Object} settings
  17158. */
  17159. self.settings = settings;
  17160. self._borderBox = self.parseBox(settings.border);
  17161. self._paddingBox = self.parseBox(settings.padding);
  17162. self._marginBox = self.parseBox(settings.margin);
  17163. if (settings.hidden) {
  17164. self.hide();
  17165. }
  17166. },
  17167. // Will generate getter/setter methods for these properties
  17168. Properties: 'parent,title,text,width,height,disabled,active,name,value',
  17169. // Will generate empty dummy functions for these
  17170. Methods: 'renderHtml',
  17171. /**
  17172. * Returns the root element to render controls into.
  17173. *
  17174. * @method getContainerElm
  17175. * @return {Element} HTML DOM element to render into.
  17176. */
  17177. getContainerElm: function() {
  17178. return document.body;
  17179. },
  17180. /**
  17181. * Returns a control instance for the current DOM element.
  17182. *
  17183. * @method getParentCtrl
  17184. * @param {Element} elm HTML dom element to get parent control from.
  17185. * @return {tinymce.ui.Control} Control instance or undefined.
  17186. */
  17187. getParentCtrl: function(elm) {
  17188. var ctrl, lookup = this.getRoot().controlIdLookup;
  17189. while (elm && lookup) {
  17190. ctrl = lookup[elm.id];
  17191. if (ctrl) {
  17192. break;
  17193. }
  17194. elm = elm.parentNode;
  17195. }
  17196. return ctrl;
  17197. },
  17198. /**
  17199. * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
  17200. *
  17201. * @method parseBox
  17202. * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
  17203. * @return {Object} Object with top/right/bottom/left properties.
  17204. * @private
  17205. */
  17206. parseBox: function(value) {
  17207. var len, radix = 10;
  17208. if (!value) {
  17209. return;
  17210. }
  17211. if (typeof(value) === "number") {
  17212. value = value || 0;
  17213. return {
  17214. top: value,
  17215. left: value,
  17216. bottom: value,
  17217. right: value
  17218. };
  17219. }
  17220. value = value.split(' ');
  17221. len = value.length;
  17222. if (len === 1) {
  17223. value[1] = value[2] = value[3] = value[0];
  17224. } else if (len === 2) {
  17225. value[2] = value[0];
  17226. value[3] = value[1];
  17227. } else if (len === 3) {
  17228. value[3] = value[1];
  17229. }
  17230. return {
  17231. top: parseInt(value[0], radix) || 0,
  17232. right: parseInt(value[1], radix) || 0,
  17233. bottom: parseInt(value[2], radix) || 0,
  17234. left: parseInt(value[3], radix) || 0
  17235. };
  17236. },
  17237. borderBox: function() {
  17238. return this._borderBox;
  17239. },
  17240. paddingBox: function() {
  17241. return this._paddingBox;
  17242. },
  17243. marginBox: function() {
  17244. return this._marginBox;
  17245. },
  17246. measureBox: function(elm, prefix) {
  17247. function getStyle(name) {
  17248. var defaultView = document.defaultView;
  17249. if (defaultView) {
  17250. // Remove camelcase
  17251. name = name.replace(/[A-Z]/g, function(a) {
  17252. return '-' + a;
  17253. });
  17254. return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  17255. }
  17256. return elm.currentStyle[name];
  17257. }
  17258. function getSide(name) {
  17259. var val = parseFloat(getStyle(name), 10);
  17260. return isNaN(val) ? 0 : val;
  17261. }
  17262. return {
  17263. top: getSide(prefix + "TopWidth"),
  17264. right: getSide(prefix + "RightWidth"),
  17265. bottom: getSide(prefix + "BottomWidth"),
  17266. left: getSide(prefix + "LeftWidth")
  17267. };
  17268. },
  17269. /**
  17270. * Initializes the current controls layout rect.
  17271. * This will be executed by the layout managers to determine the
  17272. * default minWidth/minHeight etc.
  17273. *
  17274. * @method initLayoutRect
  17275. * @return {Object} Layout rect instance.
  17276. */
  17277. initLayoutRect: function() {
  17278. var self = this, settings = self.settings, borderBox, layoutRect;
  17279. var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
  17280. var startMinWidth, startMinHeight, initialSize;
  17281. // Measure the current element
  17282. borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
  17283. self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
  17284. self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
  17285. initialSize = DomUtils.getSize(elm);
  17286. // Setup minWidth/minHeight and width/height
  17287. startMinWidth = settings.minWidth;
  17288. startMinHeight = settings.minHeight;
  17289. minWidth = startMinWidth || initialSize.width;
  17290. minHeight = startMinHeight || initialSize.height;
  17291. width = settings.width;
  17292. height = settings.height;
  17293. autoResize = settings.autoResize;
  17294. autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
  17295. width = width || minWidth;
  17296. height = height || minHeight;
  17297. var deltaW = borderBox.left + borderBox.right;
  17298. var deltaH = borderBox.top + borderBox.bottom;
  17299. var maxW = settings.maxWidth || 0xFFFF;
  17300. var maxH = settings.maxHeight || 0xFFFF;
  17301. // Setup initial layout rect
  17302. self._layoutRect = layoutRect = {
  17303. x: settings.x || 0,
  17304. y: settings.y || 0,
  17305. w: width,
  17306. h: height,
  17307. deltaW: deltaW,
  17308. deltaH: deltaH,
  17309. contentW: width - deltaW,
  17310. contentH: height - deltaH,
  17311. innerW: width - deltaW,
  17312. innerH: height - deltaH,
  17313. startMinWidth: startMinWidth || 0,
  17314. startMinHeight: startMinHeight || 0,
  17315. minW: Math.min(minWidth, maxW),
  17316. minH: Math.min(minHeight, maxH),
  17317. maxW: maxW,
  17318. maxH: maxH,
  17319. autoResize: autoResize,
  17320. scrollW: 0
  17321. };
  17322. self._lastLayoutRect = {};
  17323. return layoutRect;
  17324. },
  17325. /**
  17326. * Getter/setter for the current layout rect.
  17327. *
  17328. * @method layoutRect
  17329. * @param {Object} [newRect] Optional new layout rect.
  17330. * @return {tinymce.ui.Control/Object} Current control or rect object.
  17331. */
  17332. layoutRect: function(newRect) {
  17333. var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
  17334. // Initialize default layout rect
  17335. if (!curRect) {
  17336. curRect = self.initLayoutRect();
  17337. }
  17338. // Set new rect values
  17339. if (newRect) {
  17340. // Calc deltas between inner and outer sizes
  17341. deltaWidth = curRect.deltaW;
  17342. deltaHeight = curRect.deltaH;
  17343. // Set x position
  17344. if (newRect.x !== undef) {
  17345. curRect.x = newRect.x;
  17346. }
  17347. // Set y position
  17348. if (newRect.y !== undef) {
  17349. curRect.y = newRect.y;
  17350. }
  17351. // Set minW
  17352. if (newRect.minW !== undef) {
  17353. curRect.minW = newRect.minW;
  17354. }
  17355. // Set minH
  17356. if (newRect.minH !== undef) {
  17357. curRect.minH = newRect.minH;
  17358. }
  17359. // Set new width and calculate inner width
  17360. size = newRect.w;
  17361. if (size !== undef) {
  17362. size = size < curRect.minW ? curRect.minW : size;
  17363. size = size > curRect.maxW ? curRect.maxW : size;
  17364. curRect.w = size;
  17365. curRect.innerW = size - deltaWidth;
  17366. }
  17367. // Set new height and calculate inner height
  17368. size = newRect.h;
  17369. if (size !== undef) {
  17370. size = size < curRect.minH ? curRect.minH : size;
  17371. size = size > curRect.maxH ? curRect.maxH : size;
  17372. curRect.h = size;
  17373. curRect.innerH = size - deltaHeight;
  17374. }
  17375. // Set new inner width and calculate width
  17376. size = newRect.innerW;
  17377. if (size !== undef) {
  17378. size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
  17379. size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
  17380. curRect.innerW = size;
  17381. curRect.w = size + deltaWidth;
  17382. }
  17383. // Set new height and calculate inner height
  17384. size = newRect.innerH;
  17385. if (size !== undef) {
  17386. size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
  17387. size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
  17388. curRect.innerH = size;
  17389. curRect.h = size + deltaHeight;
  17390. }
  17391. // Set new contentW
  17392. if (newRect.contentW !== undef) {
  17393. curRect.contentW = newRect.contentW;
  17394. }
  17395. // Set new contentH
  17396. if (newRect.contentH !== undef) {
  17397. curRect.contentH = newRect.contentH;
  17398. }
  17399. // Compare last layout rect with the current one to see if we need to repaint or not
  17400. lastLayoutRect = self._lastLayoutRect;
  17401. if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
  17402. lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
  17403. repaintControls = Control.repaintControls;
  17404. if (repaintControls) {
  17405. if (repaintControls.map && !repaintControls.map[self._id]) {
  17406. repaintControls.push(self);
  17407. repaintControls.map[self._id] = true;
  17408. }
  17409. }
  17410. lastLayoutRect.x = curRect.x;
  17411. lastLayoutRect.y = curRect.y;
  17412. lastLayoutRect.w = curRect.w;
  17413. lastLayoutRect.h = curRect.h;
  17414. }
  17415. return self;
  17416. }
  17417. return curRect;
  17418. },
  17419. /**
  17420. * Repaints the control after a layout operation.
  17421. *
  17422. * @method repaint
  17423. */
  17424. repaint: function() {
  17425. var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
  17426. // Use Math.round on all values on IE < 9
  17427. round = !document.createRange ? Math.round : function(value) {
  17428. return value;
  17429. };
  17430. style = self.getEl().style;
  17431. rect = self._layoutRect;
  17432. lastRepaintRect = self._lastRepaintRect || {};
  17433. borderBox = self._borderBox;
  17434. borderW = borderBox.left + borderBox.right;
  17435. borderH = borderBox.top + borderBox.bottom;
  17436. if (rect.x !== lastRepaintRect.x) {
  17437. style.left = round(rect.x) + 'px';
  17438. lastRepaintRect.x = rect.x;
  17439. }
  17440. if (rect.y !== lastRepaintRect.y) {
  17441. style.top = round(rect.y) + 'px';
  17442. lastRepaintRect.y = rect.y;
  17443. }
  17444. if (rect.w !== lastRepaintRect.w) {
  17445. style.width = round(rect.w - borderW) + 'px';
  17446. lastRepaintRect.w = rect.w;
  17447. }
  17448. if (rect.h !== lastRepaintRect.h) {
  17449. style.height = round(rect.h - borderH) + 'px';
  17450. lastRepaintRect.h = rect.h;
  17451. }
  17452. // Update body if needed
  17453. if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
  17454. bodyStyle = self.getEl('body').style;
  17455. bodyStyle.width = round(rect.innerW) + 'px';
  17456. lastRepaintRect.innerW = rect.innerW;
  17457. }
  17458. if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
  17459. bodyStyle = bodyStyle || self.getEl('body').style;
  17460. bodyStyle.height = round(rect.innerH) + 'px';
  17461. lastRepaintRect.innerH = rect.innerH;
  17462. }
  17463. self._lastRepaintRect = lastRepaintRect;
  17464. self.fire('repaint', {}, false);
  17465. },
  17466. /**
  17467. * Binds a callback to the specified event. This event can both be
  17468. * native browser events like "click" or custom ones like PostRender.
  17469. *
  17470. * The callback function will be passed a DOM event like object that enables yout do stop propagation.
  17471. *
  17472. * @method on
  17473. * @param {String} name Name of the event to bind. For example "click".
  17474. * @param {String/function} callback Callback function to execute ones the event occurs.
  17475. * @return {tinymce.ui.Control} Current control object.
  17476. */
  17477. on: function(name, callback) {
  17478. var self = this, bindings, handlers, names, i;
  17479. function resolveCallbackName(name) {
  17480. var callback, scope;
  17481. return function(e) {
  17482. if (!callback) {
  17483. self.parentsAndSelf().each(function(ctrl) {
  17484. var callbacks = ctrl.settings.callbacks;
  17485. if (callbacks && (callback = callbacks[name])) {
  17486. scope = ctrl;
  17487. return false;
  17488. }
  17489. });
  17490. }
  17491. return callback.call(scope, e);
  17492. };
  17493. }
  17494. if (callback) {
  17495. if (typeof(callback) == 'string') {
  17496. callback = resolveCallbackName(callback);
  17497. }
  17498. names = name.toLowerCase().split(' ');
  17499. i = names.length;
  17500. while (i--) {
  17501. name = names[i];
  17502. bindings = self._bindings;
  17503. if (!bindings) {
  17504. bindings = self._bindings = {};
  17505. }
  17506. handlers = bindings[name];
  17507. if (!handlers) {
  17508. handlers = bindings[name] = [];
  17509. }
  17510. handlers.push(callback);
  17511. if (nativeEvents[name]) {
  17512. if (!self._nativeEvents) {
  17513. self._nativeEvents = {};
  17514. }
  17515. self._nativeEvents[name] = true;
  17516. if (self._rendered) {
  17517. self.bindPendingEvents();
  17518. }
  17519. }
  17520. }
  17521. }
  17522. return self;
  17523. },
  17524. /**
  17525. * Unbinds the specified event and optionally a specific callback. If you omit the name
  17526. * parameter all event handlers will be removed. If you omit the callback all event handles
  17527. * by the specified name will be removed.
  17528. *
  17529. * @method off
  17530. * @param {String} [name] Name for the event to unbind.
  17531. * @param {function} [callback] Callback function to unbind.
  17532. * @return {mxex.ui.Control} Current control object.
  17533. */
  17534. off: function(name, callback) {
  17535. var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
  17536. if (bindings) {
  17537. if (name) {
  17538. names = name.toLowerCase().split(' ');
  17539. i = names.length;
  17540. while (i--) {
  17541. name = names[i];
  17542. handlers = bindings[name];
  17543. // Unbind all handlers
  17544. if (!name) {
  17545. for (bindingName in bindings) {
  17546. bindings[bindingName].length = 0;
  17547. }
  17548. return self;
  17549. }
  17550. if (handlers) {
  17551. // Unbind all by name
  17552. if (!callback) {
  17553. handlers.length = 0;
  17554. } else {
  17555. // Unbind specific ones
  17556. hi = handlers.length;
  17557. while (hi--) {
  17558. if (handlers[hi] === callback) {
  17559. handlers.splice(hi, 1);
  17560. }
  17561. }
  17562. }
  17563. }
  17564. }
  17565. } else {
  17566. self._bindings = [];
  17567. }
  17568. }
  17569. return self;
  17570. },
  17571. /**
  17572. * Fires the specified event by name and arguments on the control. This will execute all
  17573. * bound event handlers.
  17574. *
  17575. * @method fire
  17576. * @param {String} name Name of the event to fire.
  17577. * @param {Object} [args] Arguments to pass to the event.
  17578. * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
  17579. * @return {Object} Current arguments object.
  17580. */
  17581. fire: function(name, args, bubble) {
  17582. var self = this, i, l, handlers, parentCtrl;
  17583. name = name.toLowerCase();
  17584. // Dummy function that gets replaced on the delegation state functions
  17585. function returnFalse() {
  17586. return false;
  17587. }
  17588. // Dummy function that gets replaced on the delegation state functions
  17589. function returnTrue() {
  17590. return true;
  17591. }
  17592. // Setup empty object if args is omited
  17593. args = args || {};
  17594. // Stick type into event object
  17595. if (!args.type) {
  17596. args.type = name;
  17597. }
  17598. // Stick control into event
  17599. if (!args.control) {
  17600. args.control = self;
  17601. }
  17602. // Add event delegation methods if they are missing
  17603. if (!args.preventDefault) {
  17604. // Add preventDefault method
  17605. args.preventDefault = function() {
  17606. args.isDefaultPrevented = returnTrue;
  17607. };
  17608. // Add stopPropagation
  17609. args.stopPropagation = function() {
  17610. args.isPropagationStopped = returnTrue;
  17611. };
  17612. // Add stopImmediatePropagation
  17613. args.stopImmediatePropagation = function() {
  17614. args.isImmediatePropagationStopped = returnTrue;
  17615. };
  17616. // Add event delegation states
  17617. args.isDefaultPrevented = returnFalse;
  17618. args.isPropagationStopped = returnFalse;
  17619. args.isImmediatePropagationStopped = returnFalse;
  17620. }
  17621. if (self._bindings) {
  17622. handlers = self._bindings[name];
  17623. if (handlers) {
  17624. for (i = 0, l = handlers.length; i < l; i++) {
  17625. // Execute callback and break if the callback returns a false
  17626. if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
  17627. break;
  17628. }
  17629. }
  17630. }
  17631. }
  17632. // Bubble event up to parent controls
  17633. if (bubble !== false) {
  17634. parentCtrl = self.parent();
  17635. while (parentCtrl && !args.isPropagationStopped()) {
  17636. parentCtrl.fire(name, args, false);
  17637. parentCtrl = parentCtrl.parent();
  17638. }
  17639. }
  17640. return args;
  17641. },
  17642. /**
  17643. * Returns true/false if the specified event has any listeners.
  17644. *
  17645. * @method hasEventListeners
  17646. * @param {String} name Name of the event to check for.
  17647. * @return {Boolean} True/false state if the event has listeners.
  17648. */
  17649. hasEventListeners: function(name) {
  17650. return name in this._bindings;
  17651. },
  17652. /**
  17653. * Returns a control collection with all parent controls.
  17654. *
  17655. * @method parents
  17656. * @param {String} selector Optional selector expression to find parents.
  17657. * @return {tinymce.ui.Collection} Collection with all parent controls.
  17658. */
  17659. parents: function(selector) {
  17660. var self = this, ctrl, parents = new Collection();
  17661. // Add each parent to collection
  17662. for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
  17663. parents.add(ctrl);
  17664. }
  17665. // Filter away everything that doesn't match the selector
  17666. if (selector) {
  17667. parents = parents.filter(selector);
  17668. }
  17669. return parents;
  17670. },
  17671. /**
  17672. * Returns the current control and it's parents.
  17673. *
  17674. * @method parentsAndSelf
  17675. * @param {String} selector Optional selector expression to find parents.
  17676. * @return {tinymce.ui.Collection} Collection with all parent controls.
  17677. */
  17678. parentsAndSelf: function(selector) {
  17679. return new Collection(this).add(this.parents(selector));
  17680. },
  17681. /**
  17682. * Returns the control next to the current control.
  17683. *
  17684. * @method next
  17685. * @return {tinymce.ui.Control} Next control instance.
  17686. */
  17687. next: function() {
  17688. var parentControls = this.parent().items();
  17689. return parentControls[parentControls.indexOf(this) + 1];
  17690. },
  17691. /**
  17692. * Returns the control previous to the current control.
  17693. *
  17694. * @method prev
  17695. * @return {tinymce.ui.Control} Previous control instance.
  17696. */
  17697. prev: function() {
  17698. var parentControls = this.parent().items();
  17699. return parentControls[parentControls.indexOf(this) - 1];
  17700. },
  17701. /**
  17702. * Find the common ancestor for two control instances.
  17703. *
  17704. * @method findCommonAncestor
  17705. * @param {tinymce.ui.Control} ctrl1 First control.
  17706. * @param {tinymce.ui.Control} ctrl2 Second control.
  17707. * @return {tinymce.ui.Control} Ancestor control instance.
  17708. */
  17709. findCommonAncestor: function(ctrl1, ctrl2) {
  17710. var parentCtrl;
  17711. while (ctrl1) {
  17712. parentCtrl = ctrl2;
  17713. while (parentCtrl && ctrl1 != parentCtrl) {
  17714. parentCtrl = parentCtrl.parent();
  17715. }
  17716. if (ctrl1 == parentCtrl) {
  17717. break;
  17718. }
  17719. ctrl1 = ctrl1.parent();
  17720. }
  17721. return ctrl1;
  17722. },
  17723. /**
  17724. * Returns true/false if the specific control has the specific class.
  17725. *
  17726. * @method hasClass
  17727. * @param {String} cls Class to check for.
  17728. * @param {String} [group] Sub element group name.
  17729. * @return {Boolean} True/false if the control has the specified class.
  17730. */
  17731. hasClass: function(cls, group) {
  17732. var classes = this._classes[group || 'control'];
  17733. cls = this.classPrefix + cls;
  17734. return classes && !!classes.map[cls];
  17735. },
  17736. /**
  17737. * Adds the specified class to the control
  17738. *
  17739. * @method addClass
  17740. * @param {String} cls Class to check for.
  17741. * @param {String} [group] Sub element group name.
  17742. * @return {tinymce.ui.Control} Current control object.
  17743. */
  17744. addClass: function(cls, group) {
  17745. var self = this, classes, elm;
  17746. cls = this.classPrefix + cls;
  17747. classes = self._classes[group || 'control'];
  17748. if (!classes) {
  17749. classes = [];
  17750. classes.map = {};
  17751. self._classes[group || 'control'] = classes;
  17752. }
  17753. if (!classes.map[cls]) {
  17754. classes.map[cls] = cls;
  17755. classes.push(cls);
  17756. if (self._rendered) {
  17757. elm = self.getEl(group);
  17758. if (elm) {
  17759. elm.className = classes.join(' ');
  17760. }
  17761. }
  17762. }
  17763. return self;
  17764. },
  17765. /**
  17766. * Removes the specified class from the control.
  17767. *
  17768. * @method removeClass
  17769. * @param {String} cls Class to remove.
  17770. * @param {String} [group] Sub element group name.
  17771. * @return {tinymce.ui.Control} Current control object.
  17772. */
  17773. removeClass: function(cls, group) {
  17774. var self = this, classes, i, elm;
  17775. cls = this.classPrefix + cls;
  17776. classes = self._classes[group || 'control'];
  17777. if (classes && classes.map[cls]) {
  17778. delete classes.map[cls];
  17779. i = classes.length;
  17780. while (i--) {
  17781. if (classes[i] === cls) {
  17782. classes.splice(i, 1);
  17783. }
  17784. }
  17785. }
  17786. if (self._rendered) {
  17787. elm = self.getEl(group);
  17788. if (elm) {
  17789. elm.className = classes.join(' ');
  17790. }
  17791. }
  17792. return self;
  17793. },
  17794. /**
  17795. * Toggles the specified class on the control.
  17796. *
  17797. * @method toggleClass
  17798. * @param {String} cls Class to remove.
  17799. * @param {Boolean} state True/false state to add/remove class.
  17800. * @param {String} [group] Sub element group name.
  17801. * @return {tinymce.ui.Control} Current control object.
  17802. */
  17803. toggleClass: function(cls, state, group) {
  17804. var self = this;
  17805. if (state) {
  17806. self.addClass(cls, group);
  17807. } else {
  17808. self.removeClass(cls, group);
  17809. }
  17810. return self;
  17811. },
  17812. /**
  17813. * Returns the class string for the specified group name.
  17814. *
  17815. * @method classes
  17816. * @param {String} [group] Group to get clases by.
  17817. * @return {String} Classes for the specified group.
  17818. */
  17819. classes: function(group) {
  17820. var classes = this._classes[group || 'control'];
  17821. return classes ? classes.join(' ') : '';
  17822. },
  17823. /**
  17824. * Sets the inner HTML of the control element.
  17825. *
  17826. * @method innerHtml
  17827. * @param {String} html Html string to set as inner html.
  17828. * @return {tinymce.ui.Control} Current control object.
  17829. */
  17830. innerHtml: function(html) {
  17831. DomUtils.innerHtml(this.getEl(), html);
  17832. return this;
  17833. },
  17834. /**
  17835. * Returns the control DOM element or sub element.
  17836. *
  17837. * @method getEl
  17838. * @param {String} [suffix] Suffix to get element by.
  17839. * @param {Boolean} [dropCache] True if the cache for the element should be dropped.
  17840. * @return {Element} HTML DOM element for the current control or it's children.
  17841. */
  17842. getEl: function(suffix, dropCache) {
  17843. var elm, id = suffix ? this._id + '-' + suffix : this._id;
  17844. elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);
  17845. return elm;
  17846. },
  17847. /**
  17848. * Sets/gets the visible for the control.
  17849. *
  17850. * @method visible
  17851. * @param {Boolean} state Value to set to control.
  17852. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  17853. */
  17854. visible: function(state) {
  17855. var self = this, parentCtrl;
  17856. if (typeof(state) !== "undefined") {
  17857. if (self._visible !== state) {
  17858. if (self._rendered) {
  17859. self.getEl().style.display = state ? '' : 'none';
  17860. }
  17861. self._visible = state;
  17862. // Parent container needs to reflow
  17863. parentCtrl = self.parent();
  17864. if (parentCtrl) {
  17865. parentCtrl._lastRect = null;
  17866. }
  17867. self.fire(state ? 'show' : 'hide');
  17868. }
  17869. return self;
  17870. }
  17871. return self._visible;
  17872. },
  17873. /**
  17874. * Sets the visible state to true.
  17875. *
  17876. * @method show
  17877. * @return {tinymce.ui.Control} Current control instance.
  17878. */
  17879. show: function() {
  17880. return this.visible(true);
  17881. },
  17882. /**
  17883. * Sets the visible state to false.
  17884. *
  17885. * @method hide
  17886. * @return {tinymce.ui.Control} Current control instance.
  17887. */
  17888. hide: function() {
  17889. return this.visible(false);
  17890. },
  17891. /**
  17892. * Focuses the current control.
  17893. *
  17894. * @method focus
  17895. * @return {tinymce.ui.Control} Current control instance.
  17896. */
  17897. focus: function() {
  17898. try {
  17899. this.getEl().focus();
  17900. } catch (ex) {
  17901. // Ignore IE error
  17902. }
  17903. return this;
  17904. },
  17905. /**
  17906. * Blurs the current control.
  17907. *
  17908. * @method blur
  17909. * @return {tinymce.ui.Control} Current control instance.
  17910. */
  17911. blur: function() {
  17912. this.getEl().blur();
  17913. return this;
  17914. },
  17915. /**
  17916. * Sets the specified aria property.
  17917. *
  17918. * @method aria
  17919. * @param {String} name Name of the aria property to set.
  17920. * @param {String} value Value of the aria property.
  17921. * @return {tinymce.ui.Control} Current control instance.
  17922. */
  17923. aria: function(name, value) {
  17924. var self = this, elm = self.getEl(self.ariaTarget);
  17925. if (typeof(value) === "undefined") {
  17926. return self._aria[name];
  17927. } else {
  17928. self._aria[name] = value;
  17929. }
  17930. if (self._rendered) {
  17931. elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
  17932. }
  17933. return self;
  17934. },
  17935. /**
  17936. * Encodes the specified string with HTML entities. It will also
  17937. * translate the string to different languages.
  17938. *
  17939. * @method encode
  17940. * @param {String/Object/Array} text Text to entity encode.
  17941. * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
  17942. * @return {String} Encoded and possible traslated string.
  17943. */
  17944. encode: function(text, translate) {
  17945. if (translate !== false) {
  17946. text = this.translate(text);
  17947. }
  17948. return (text || '').replace(/[&<>"]/g, function(match) {
  17949. return '&#' + match.charCodeAt(0) + ';';
  17950. });
  17951. },
  17952. /**
  17953. * Returns the translated string.
  17954. *
  17955. * @method translate
  17956. * @param {String} text Text to translate.
  17957. * @return {String} Translated string or the same as the input.
  17958. */
  17959. translate: function(text) {
  17960. return Control.translate ? Control.translate(text) : text;
  17961. },
  17962. /**
  17963. * Adds items before the current control.
  17964. *
  17965. * @method before
  17966. * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
  17967. * @return {tinymce.ui.Control} Current control instance.
  17968. */
  17969. before: function(items) {
  17970. var self = this, parent = self.parent();
  17971. if (parent) {
  17972. parent.insert(items, parent.items().indexOf(self), true);
  17973. }
  17974. return self;
  17975. },
  17976. /**
  17977. * Adds items after the current control.
  17978. *
  17979. * @method after
  17980. * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
  17981. * @return {tinymce.ui.Control} Current control instance.
  17982. */
  17983. after: function(items) {
  17984. var self = this, parent = self.parent();
  17985. if (parent) {
  17986. parent.insert(items, parent.items().indexOf(self));
  17987. }
  17988. return self;
  17989. },
  17990. /**
  17991. * Removes the current control from DOM and from UI collections.
  17992. *
  17993. * @method remove
  17994. * @return {tinymce.ui.Control} Current control instance.
  17995. */
  17996. remove: function() {
  17997. var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
  17998. if (self.items) {
  17999. var controls = self.items().toArray();
  18000. i = controls.length;
  18001. while (i--) {
  18002. controls[i].remove();
  18003. }
  18004. }
  18005. if (parent && parent.items) {
  18006. newItems = [];
  18007. parent.items().each(function(item) {
  18008. if (item !== self) {
  18009. newItems.push(item);
  18010. }
  18011. });
  18012. parent.items().set(newItems);
  18013. parent._lastRect = null;
  18014. }
  18015. if (self._eventsRoot && self._eventsRoot == self) {
  18016. DomUtils.off(elm);
  18017. }
  18018. var lookup = self.getRoot().controlIdLookup;
  18019. if (lookup) {
  18020. delete lookup[self._id];
  18021. }
  18022. delete elementIdCache[self._id];
  18023. if (elm && elm.parentNode) {
  18024. var nodes = elm.getElementsByTagName('*');
  18025. i = nodes.length;
  18026. while (i--) {
  18027. delete elementIdCache[nodes[i].id];
  18028. }
  18029. elm.parentNode.removeChild(elm);
  18030. }
  18031. self._rendered = false;
  18032. return self;
  18033. },
  18034. /**
  18035. * Renders the control before the specified element.
  18036. *
  18037. * @method renderBefore
  18038. * @param {Element} elm Element to render before.
  18039. * @return {tinymce.ui.Control} Current control instance.
  18040. */
  18041. renderBefore: function(elm) {
  18042. var self = this;
  18043. elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
  18044. self.postRender();
  18045. return self;
  18046. },
  18047. /**
  18048. * Renders the control to the specified element.
  18049. *
  18050. * @method renderBefore
  18051. * @param {Element} elm Element to render to.
  18052. * @return {tinymce.ui.Control} Current control instance.
  18053. */
  18054. renderTo: function(elm) {
  18055. var self = this;
  18056. elm = elm || self.getContainerElm();
  18057. elm.appendChild(DomUtils.createFragment(self.renderHtml()));
  18058. self.postRender();
  18059. return self;
  18060. },
  18061. /**
  18062. * Post render method. Called after the control has been rendered to the target.
  18063. *
  18064. * @method postRender
  18065. * @return {tinymce.ui.Control} Current control instance.
  18066. */
  18067. postRender: function() {
  18068. var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
  18069. // Bind on<event> settings
  18070. for (name in settings) {
  18071. if (name.indexOf("on") === 0) {
  18072. self.on(name.substr(2), settings[name]);
  18073. }
  18074. }
  18075. if (self._eventsRoot) {
  18076. for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
  18077. parentEventsRoot = parent._eventsRoot;
  18078. }
  18079. if (parentEventsRoot) {
  18080. for (name in parentEventsRoot._nativeEvents) {
  18081. self._nativeEvents[name] = true;
  18082. }
  18083. }
  18084. }
  18085. self.bindPendingEvents();
  18086. if (settings.style) {
  18087. elm = self.getEl();
  18088. if (elm) {
  18089. elm.setAttribute('style', settings.style);
  18090. elm.style.cssText = settings.style;
  18091. }
  18092. }
  18093. if (!self._visible) {
  18094. DomUtils.css(self.getEl(), 'display', 'none');
  18095. }
  18096. if (self.settings.border) {
  18097. box = self.borderBox();
  18098. DomUtils.css(self.getEl(), {
  18099. 'border-top-width': box.top,
  18100. 'border-right-width': box.right,
  18101. 'border-bottom-width': box.bottom,
  18102. 'border-left-width': box.left
  18103. });
  18104. }
  18105. // Add instance to lookup
  18106. var root = self.getRoot();
  18107. if (!root.controlIdLookup) {
  18108. root.controlIdLookup = {};
  18109. }
  18110. root.controlIdLookup[self._id] = self;
  18111. for (var key in self._aria) {
  18112. self.aria(key, self._aria[key]);
  18113. }
  18114. self.fire('postrender', {}, false);
  18115. },
  18116. /**
  18117. * Scrolls the current control into view.
  18118. *
  18119. * @method scrollIntoView
  18120. * @param {String} align Alignment in view top|center|bottom.
  18121. * @return {tinymce.ui.Control} Current control instance.
  18122. */
  18123. scrollIntoView: function(align) {
  18124. function getOffset(elm, rootElm) {
  18125. var x, y, parent = elm;
  18126. x = y = 0;
  18127. while (parent && parent != rootElm && parent.nodeType) {
  18128. x += parent.offsetLeft || 0;
  18129. y += parent.offsetTop || 0;
  18130. parent = parent.offsetParent;
  18131. }
  18132. return {x: x, y: y};
  18133. }
  18134. var elm = this.getEl(), parentElm = elm.parentNode;
  18135. var x, y, width, height, parentWidth, parentHeight;
  18136. var pos = getOffset(elm, parentElm);
  18137. x = pos.x;
  18138. y = pos.y;
  18139. width = elm.offsetWidth;
  18140. height = elm.offsetHeight;
  18141. parentWidth = parentElm.clientWidth;
  18142. parentHeight = parentElm.clientHeight;
  18143. if (align == "end") {
  18144. x -= parentWidth - width;
  18145. y -= parentHeight - height;
  18146. } else if (align == "center") {
  18147. x -= (parentWidth / 2) - (width / 2);
  18148. y -= (parentHeight / 2) - (height / 2);
  18149. }
  18150. parentElm.scrollLeft = x;
  18151. parentElm.scrollTop = y;
  18152. return this;
  18153. },
  18154. /**
  18155. * Binds pending DOM events.
  18156. *
  18157. * @private
  18158. */
  18159. bindPendingEvents: function() {
  18160. var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
  18161. function delegate(e) {
  18162. var control = self.getParentCtrl(e.target);
  18163. if (control) {
  18164. control.fire(e.type, e);
  18165. }
  18166. }
  18167. function mouseLeaveHandler() {
  18168. var ctrl = eventRootCtrl._lastHoverCtrl;
  18169. if (ctrl) {
  18170. ctrl.fire("mouseleave", {target: ctrl.getEl()});
  18171. ctrl.parents().each(function(ctrl) {
  18172. ctrl.fire("mouseleave", {target: ctrl.getEl()});
  18173. });
  18174. eventRootCtrl._lastHoverCtrl = null;
  18175. }
  18176. }
  18177. function mouseEnterHandler(e) {
  18178. var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
  18179. // Over on a new control
  18180. if (ctrl !== lastCtrl) {
  18181. eventRootCtrl._lastHoverCtrl = ctrl;
  18182. parents = ctrl.parents().toArray().reverse();
  18183. parents.push(ctrl);
  18184. if (lastCtrl) {
  18185. lastParents = lastCtrl.parents().toArray().reverse();
  18186. lastParents.push(lastCtrl);
  18187. for (idx = 0; idx < lastParents.length; idx++) {
  18188. if (parents[idx] !== lastParents[idx]) {
  18189. break;
  18190. }
  18191. }
  18192. for (i = lastParents.length - 1; i >= idx; i--) {
  18193. lastCtrl = lastParents[i];
  18194. lastCtrl.fire("mouseleave", {
  18195. target : lastCtrl.getEl()
  18196. });
  18197. }
  18198. }
  18199. for (i = idx; i < parents.length; i++) {
  18200. ctrl = parents[i];
  18201. ctrl.fire("mouseenter", {
  18202. target : ctrl.getEl()
  18203. });
  18204. }
  18205. }
  18206. }
  18207. function fixWheelEvent(e) {
  18208. e.preventDefault();
  18209. if (e.type == "mousewheel") {
  18210. e.deltaY = -1 / 40 * e.wheelDelta;
  18211. if (e.wheelDeltaX) {
  18212. e.deltaX = -1 / 40 * e.wheelDeltaX;
  18213. }
  18214. } else {
  18215. e.deltaX = 0;
  18216. e.deltaY = e.detail;
  18217. }
  18218. e = self.fire("wheel", e);
  18219. }
  18220. self._rendered = true;
  18221. nativeEvents = self._nativeEvents;
  18222. if (nativeEvents) {
  18223. // Find event root element if it exists
  18224. parents = self.parents().toArray();
  18225. parents.unshift(self);
  18226. for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
  18227. eventRootCtrl = parents[i]._eventsRoot;
  18228. }
  18229. // Event root wasn't found the use the root control
  18230. if (!eventRootCtrl) {
  18231. eventRootCtrl = parents[parents.length - 1] || self;
  18232. }
  18233. // Set the eventsRoot property on children that didn't have it
  18234. self._eventsRoot = eventRootCtrl;
  18235. for (l = i, i = 0; i < l; i++) {
  18236. parents[i]._eventsRoot = eventRootCtrl;
  18237. }
  18238. var eventRootDelegates = eventRootCtrl._delegates;
  18239. if (!eventRootDelegates) {
  18240. eventRootDelegates = eventRootCtrl._delegates = {};
  18241. }
  18242. // Bind native event delegates
  18243. for (name in nativeEvents) {
  18244. if (!nativeEvents) {
  18245. return false;
  18246. }
  18247. if (name === "wheel" && !hasWheelEventSupport) {
  18248. if (hasMouseWheelEventSupport) {
  18249. DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
  18250. } else {
  18251. DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
  18252. }
  18253. continue;
  18254. }
  18255. // Special treatment for mousenter/mouseleave since these doesn't bubble
  18256. if (name === "mouseenter" || name === "mouseleave") {
  18257. // Fake mousenter/mouseleave
  18258. if (!eventRootCtrl._hasMouseEnter) {
  18259. DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
  18260. DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
  18261. eventRootCtrl._hasMouseEnter = 1;
  18262. }
  18263. } else if (!eventRootDelegates[name]) {
  18264. DomUtils.on(eventRootCtrl.getEl(), name, delegate);
  18265. eventRootDelegates[name] = true;
  18266. }
  18267. // Remove the event once it's bound
  18268. nativeEvents[name] = false;
  18269. }
  18270. }
  18271. },
  18272. getRoot: function() {
  18273. var ctrl = this, rootControl, parents = [];
  18274. while (ctrl) {
  18275. if (ctrl.rootControl) {
  18276. rootControl = ctrl.rootControl;
  18277. break;
  18278. }
  18279. parents.push(ctrl);
  18280. rootControl = ctrl;
  18281. ctrl = ctrl.parent();
  18282. }
  18283. if (!rootControl) {
  18284. rootControl = this;
  18285. }
  18286. var i = parents.length;
  18287. while (i--) {
  18288. parents[i].rootControl = rootControl;
  18289. }
  18290. return rootControl;
  18291. },
  18292. /**
  18293. * Reflows the current control and it's parents.
  18294. * This should be used after you for example append children to the current control so
  18295. * that the layout managers know that they need to reposition everything.
  18296. *
  18297. * @example
  18298. * container.append({type: 'button', text: 'My button'}).reflow();
  18299. *
  18300. * @method reflow
  18301. * @return {tinymce.ui.Control} Current control instance.
  18302. */
  18303. reflow: function() {
  18304. this.repaint();
  18305. return this;
  18306. }
  18307. /**
  18308. * Sets/gets the parent container for the control.
  18309. *
  18310. * @method parent
  18311. * @param {tinymce.ui.Container} parent Optional parent to set.
  18312. * @return {tinymce.ui.Control} Parent control or the current control on a set action.
  18313. */
  18314. // parent: function(parent) {} -- Generated
  18315. /**
  18316. * Sets/gets the text for the control.
  18317. *
  18318. * @method text
  18319. * @param {String} value Value to set to control.
  18320. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18321. */
  18322. // text: function(value) {} -- Generated
  18323. /**
  18324. * Sets/gets the width for the control.
  18325. *
  18326. * @method width
  18327. * @param {Number} value Value to set to control.
  18328. * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18329. */
  18330. // width: function(value) {} -- Generated
  18331. /**
  18332. * Sets/gets the height for the control.
  18333. *
  18334. * @method height
  18335. * @param {Number} value Value to set to control.
  18336. * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18337. */
  18338. // height: function(value) {} -- Generated
  18339. /**
  18340. * Sets/gets the disabled state on the control.
  18341. *
  18342. * @method disabled
  18343. * @param {Boolean} state Value to set to control.
  18344. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  18345. */
  18346. // disabled: function(state) {} -- Generated
  18347. /**
  18348. * Sets/gets the active for the control.
  18349. *
  18350. * @method active
  18351. * @param {Boolean} state Value to set to control.
  18352. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  18353. */
  18354. // active: function(state) {} -- Generated
  18355. /**
  18356. * Sets/gets the name for the control.
  18357. *
  18358. * @method name
  18359. * @param {String} value Value to set to control.
  18360. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18361. */
  18362. // name: function(value) {} -- Generated
  18363. /**
  18364. * Sets/gets the title for the control.
  18365. *
  18366. * @method title
  18367. * @param {String} value Value to set to control.
  18368. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18369. */
  18370. // title: function(value) {} -- Generated
  18371. });
  18372. return Control;
  18373. });
  18374. // Included from: js/tinymce/classes/ui/Factory.js
  18375. /**
  18376. * Factory.js
  18377. *
  18378. * Copyright, Moxiecode Systems AB
  18379. * Released under LGPL License.
  18380. *
  18381. * License: http://www.tinymce.com/license
  18382. * Contributing: http://www.tinymce.com/contributing
  18383. */
  18384. /*global tinymce:true */
  18385. /**
  18386. * This class is a factory for control instances. This enables you
  18387. * to create instances of controls without having to require the UI controls directly.
  18388. *
  18389. * It also allow you to override or add new control types.
  18390. *
  18391. * @class tinymce.ui.Factory
  18392. */
  18393. define("tinymce/ui/Factory", [], function() {
  18394. "use strict";
  18395. var types = {}, namespaceInit;
  18396. return {
  18397. /**
  18398. * Adds a new control instance type to the factory.
  18399. *
  18400. * @method add
  18401. * @param {String} type Type name for example "button".
  18402. * @param {function} typeClass Class type function.
  18403. */
  18404. add: function(type, typeClass) {
  18405. types[type.toLowerCase()] = typeClass;
  18406. },
  18407. /**
  18408. * Returns true/false if the specified type exists or not.
  18409. *
  18410. * @method has
  18411. * @param {String} type Type to look for.
  18412. * @return {Boolean} true/false if the control by name exists.
  18413. */
  18414. has: function(type) {
  18415. return !!types[type.toLowerCase()];
  18416. },
  18417. /**
  18418. * Creates a new control instance based on the settings provided. The instance created will be
  18419. * based on the specified type property it can also create whole structures of components out of
  18420. * the specified JSON object.
  18421. *
  18422. * @example
  18423. * tinymce.ui.Factory.create({
  18424. * type: 'button',
  18425. * text: 'Hello world!'
  18426. * });
  18427. *
  18428. * @method create
  18429. * @param {Object/String} settings Name/Value object with items used to create the type.
  18430. * @return {tinymce.ui.Control} Control instance based on the specified type.
  18431. */
  18432. create: function(type, settings) {
  18433. var ControlType, name, namespace;
  18434. // Build type lookup
  18435. if (!namespaceInit) {
  18436. namespace = tinymce.ui;
  18437. for (name in namespace) {
  18438. types[name.toLowerCase()] = namespace[name];
  18439. }
  18440. namespaceInit = true;
  18441. }
  18442. // If string is specified then use it as the type
  18443. if (typeof(type) == 'string') {
  18444. settings = settings || {};
  18445. settings.type = type;
  18446. } else {
  18447. settings = type;
  18448. type = settings.type;
  18449. }
  18450. // Find control type
  18451. type = type.toLowerCase();
  18452. ControlType = types[type];
  18453. // #if debug
  18454. if (!ControlType) {
  18455. throw new Error("Could not find control by type: " + type);
  18456. }
  18457. // #endif
  18458. ControlType = new ControlType(settings);
  18459. ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
  18460. return ControlType;
  18461. }
  18462. };
  18463. });
  18464. // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
  18465. /**
  18466. * KeyboardNavigation.js
  18467. *
  18468. * Copyright, Moxiecode Systems AB
  18469. * Released under LGPL License.
  18470. *
  18471. * License: http://www.tinymce.com/license
  18472. * Contributing: http://www.tinymce.com/contributing
  18473. */
  18474. /**
  18475. * This class handles keyboard navigation of controls and elements.
  18476. *
  18477. * @class tinymce.ui.KeyboardNavigation
  18478. */
  18479. define("tinymce/ui/KeyboardNavigation", [
  18480. ], function() {
  18481. "use strict";
  18482. /**
  18483. * This class handles all keyboard navigation for WAI-ARIA support. Each root container
  18484. * gets an instance of this class.
  18485. *
  18486. * @constructor
  18487. */
  18488. return function(settings) {
  18489. var root = settings.root, focusedElement, focusedControl;
  18490. focusedElement = document.activeElement;
  18491. focusedControl = root.getParentCtrl(focusedElement);
  18492. /**
  18493. * Returns the currently focused elements wai aria role of the currently
  18494. * focused element or specified element.
  18495. *
  18496. * @private
  18497. * @param {Element} elm Optional element to get role from.
  18498. * @return {String} Role of specified element.
  18499. */
  18500. function getRole(elm) {
  18501. elm = elm || focusedElement;
  18502. return elm && elm.getAttribute('role');
  18503. }
  18504. /**
  18505. * Returns the wai role of the parent element of the currently
  18506. * focused element or specified element.
  18507. *
  18508. * @private
  18509. * @param {Element} elm Optional element to get parent role from.
  18510. * @return {String} Role of the first parent that has a role.
  18511. */
  18512. function getParentRole(elm) {
  18513. var role, parent = elm || focusedElement;
  18514. while ((parent = parent.parentNode)) {
  18515. if ((role = getRole(parent))) {
  18516. return role;
  18517. }
  18518. }
  18519. }
  18520. /**
  18521. * Returns a wai aria property by name for example aria-selected.
  18522. *
  18523. * @private
  18524. * @param {String} name Name of the aria property to get for example "disabled".
  18525. * @return {String} Aria property value.
  18526. */
  18527. function getAriaProp(name) {
  18528. var elm = focusedElement;
  18529. if (elm) {
  18530. return elm.getAttribute('aria-' + name);
  18531. }
  18532. }
  18533. /**
  18534. * Is the element a text input element or not.
  18535. *
  18536. * @private
  18537. * @param {Element} elm Element to check if it's an text input element or not.
  18538. * @return {Boolean} True/false if the element is a text element or not.
  18539. */
  18540. function isTextInputElement(elm) {
  18541. var tagName = elm.tagName.toUpperCase();
  18542. // Notice: since type can be "email" etc we don't check the type
  18543. // So all input elements gets treated as text input elements
  18544. return tagName == "INPUT" || tagName == "TEXTAREA";
  18545. }
  18546. /**
  18547. * Returns true/false if the specified element can be focused or not.
  18548. *
  18549. * @private
  18550. * @param {Element} elm DOM element to check if it can be focused or not.
  18551. * @return {Boolean} True/false if the element can have focus.
  18552. */
  18553. function canFocus(elm) {
  18554. if (isTextInputElement(elm) && !elm.hidden) {
  18555. return true;
  18556. }
  18557. if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
  18558. return true;
  18559. }
  18560. return false;
  18561. }
  18562. /**
  18563. * Returns an array of focusable visible elements within the specified container element.
  18564. *
  18565. * @private
  18566. * @param {Element} elm DOM element to find focusable elements within.
  18567. * @return {Array} Array of focusable elements.
  18568. */
  18569. function getFocusElements(elm) {
  18570. var elements = [];
  18571. function collect(elm) {
  18572. if (elm.nodeType != 1 || elm.style.display == 'none') {
  18573. return;
  18574. }
  18575. if (canFocus(elm)) {
  18576. elements.push(elm);
  18577. }
  18578. for (var i = 0; i < elm.childNodes.length; i++) {
  18579. collect(elm.childNodes[i]);
  18580. }
  18581. }
  18582. collect(elm || root.getEl());
  18583. return elements;
  18584. }
  18585. /**
  18586. * Returns the navigation root control for the specified control. The navigation root
  18587. * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
  18588. * It will look for parents of the specified target control or the currenty focused control if this option is omitted.
  18589. *
  18590. * @private
  18591. * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
  18592. * @return {tinymce.ui.Control} Navigation root control.
  18593. */
  18594. function getNavigationRoot(targetControl) {
  18595. var navigationRoot, controls;
  18596. targetControl = targetControl || focusedControl;
  18597. controls = targetControl.parents().toArray();
  18598. controls.unshift(targetControl);
  18599. for (var i = 0; i < controls.length; i++) {
  18600. navigationRoot = controls[i];
  18601. if (navigationRoot.settings.ariaRoot) {
  18602. break;
  18603. }
  18604. }
  18605. return navigationRoot;
  18606. }
  18607. /**
  18608. * Focuses the first item in the specified targetControl element or the last aria index if the
  18609. * navigation root has the ariaRemember option enabled.
  18610. *
  18611. * @private
  18612. * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
  18613. */
  18614. function focusFirst(targetControl) {
  18615. var navigationRoot = getNavigationRoot(targetControl);
  18616. var focusElements = getFocusElements(navigationRoot.getEl());
  18617. if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
  18618. moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
  18619. } else {
  18620. moveFocusToIndex(0, focusElements);
  18621. }
  18622. }
  18623. /**
  18624. * Moves the focus to the specified index within the elements list.
  18625. * This will scope the index to the size of the element list if it changed.
  18626. *
  18627. * @private
  18628. * @param {Number} idx Specified index to move to.
  18629. * @param {Array} elements Array with dom elements to move focus within.
  18630. * @return {Number} Input index or a changed index if it was out of range.
  18631. */
  18632. function moveFocusToIndex(idx, elements) {
  18633. if (idx < 0) {
  18634. idx = elements.length - 1;
  18635. } else if (idx >= elements.length) {
  18636. idx = 0;
  18637. }
  18638. if (elements[idx]) {
  18639. elements[idx].focus();
  18640. }
  18641. return idx;
  18642. }
  18643. /**
  18644. * Moves the focus forwards or backwards.
  18645. *
  18646. * @private
  18647. * @param {Number} dir Direction to move in positive means forward, negative means backwards.
  18648. * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
  18649. */
  18650. function moveFocus(dir, elements) {
  18651. var idx = -1, navigationRoot = getNavigationRoot();
  18652. elements = elements || getFocusElements(navigationRoot.getEl());
  18653. for (var i = 0; i < elements.length; i++) {
  18654. if (elements[i] === focusedElement) {
  18655. idx = i;
  18656. }
  18657. }
  18658. idx += dir;
  18659. navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
  18660. }
  18661. /**
  18662. * Moves the focus to the left this is called by the left key.
  18663. *
  18664. * @private
  18665. */
  18666. function left() {
  18667. var parentRole = getParentRole();
  18668. if (parentRole == "tablist") {
  18669. moveFocus(-1, getFocusElements(focusedElement.parentNode));
  18670. } else if (focusedControl.parent().submenu) {
  18671. cancel();
  18672. } else {
  18673. moveFocus(-1);
  18674. }
  18675. }
  18676. /**
  18677. * Moves the focus to the right this is called by the right key.
  18678. *
  18679. * @private
  18680. */
  18681. function right() {
  18682. var role = getRole(), parentRole = getParentRole();
  18683. if (parentRole == "tablist") {
  18684. moveFocus(1, getFocusElements(focusedElement.parentNode));
  18685. } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
  18686. enter();
  18687. } else {
  18688. moveFocus(1);
  18689. }
  18690. }
  18691. /**
  18692. * Moves the focus to the up this is called by the up key.
  18693. *
  18694. * @private
  18695. */
  18696. function up() {
  18697. moveFocus(-1);
  18698. }
  18699. /**
  18700. * Moves the focus to the up this is called by the down key.
  18701. *
  18702. * @private
  18703. */
  18704. function down() {
  18705. var role = getRole(), parentRole = getParentRole();
  18706. if (role == "menuitem" && parentRole == "menubar") {
  18707. enter();
  18708. } else if (role == "button" && getAriaProp('haspopup')) {
  18709. enter({key: 'down'});
  18710. } else {
  18711. moveFocus(1);
  18712. }
  18713. }
  18714. /**
  18715. * Moves the focus to the next item or previous item depending on shift key.
  18716. *
  18717. * @private
  18718. * @param {DOMEvent} e DOM event object.
  18719. */
  18720. function tab(e) {
  18721. var parentRole = getParentRole();
  18722. if (parentRole == "tablist") {
  18723. var elm = getFocusElements(focusedControl.getEl('body'))[0];
  18724. if (elm) {
  18725. elm.focus();
  18726. }
  18727. } else {
  18728. moveFocus(e.shiftKey ? -1 : 1);
  18729. }
  18730. }
  18731. /**
  18732. * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
  18733. *
  18734. * @private
  18735. */
  18736. function cancel() {
  18737. focusedControl.fire('cancel');
  18738. }
  18739. /**
  18740. * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
  18741. *
  18742. * @private
  18743. * @param {Object} aria Optional aria data to pass along with the enter event.
  18744. */
  18745. function enter(aria) {
  18746. aria = aria || {};
  18747. focusedControl.fire('click', {target: focusedElement, aria: aria});
  18748. }
  18749. root.on('keydown', function(e) {
  18750. function handleNonTabOrEscEvent(e, handler) {
  18751. // Ignore non tab keys for text elements
  18752. if (isTextInputElement(focusedElement)) {
  18753. return;
  18754. }
  18755. if (handler(e) !== false) {
  18756. e.preventDefault();
  18757. }
  18758. }
  18759. if (e.isDefaultPrevented()) {
  18760. return;
  18761. }
  18762. switch (e.keyCode) {
  18763. case 37: // DOM_VK_LEFT
  18764. handleNonTabOrEscEvent(e, left);
  18765. break;
  18766. case 39: // DOM_VK_RIGHT
  18767. handleNonTabOrEscEvent(e, right);
  18768. break;
  18769. case 38: // DOM_VK_UP
  18770. handleNonTabOrEscEvent(e, up);
  18771. break;
  18772. case 40: // DOM_VK_DOWN
  18773. handleNonTabOrEscEvent(e, down);
  18774. break;
  18775. case 27: // DOM_VK_ESCAPE
  18776. cancel();
  18777. break;
  18778. case 14: // DOM_VK_ENTER
  18779. case 13: // DOM_VK_RETURN
  18780. case 32: // DOM_VK_SPACE
  18781. handleNonTabOrEscEvent(e, enter);
  18782. break;
  18783. case 9: // DOM_VK_TAB
  18784. if (tab(e) !== false) {
  18785. e.preventDefault();
  18786. }
  18787. break;
  18788. }
  18789. });
  18790. root.on('focusin', function(e) {
  18791. focusedElement = e.target;
  18792. focusedControl = e.control;
  18793. });
  18794. return {
  18795. focusFirst: focusFirst
  18796. };
  18797. };
  18798. });
  18799. // Included from: js/tinymce/classes/ui/Container.js
  18800. /**
  18801. * Container.js
  18802. *
  18803. * Copyright, Moxiecode Systems AB
  18804. * Released under LGPL License.
  18805. *
  18806. * License: http://www.tinymce.com/license
  18807. * Contributing: http://www.tinymce.com/contributing
  18808. */
  18809. /**
  18810. * Container control. This is extended by all controls that can have
  18811. * children such as panels etc. You can also use this class directly as an
  18812. * generic container instance. The container doesn't have any specific role or style.
  18813. *
  18814. * @-x-less Container.less
  18815. * @class tinymce.ui.Container
  18816. * @extends tinymce.ui.Control
  18817. */
  18818. define("tinymce/ui/Container", [
  18819. "tinymce/ui/Control",
  18820. "tinymce/ui/Collection",
  18821. "tinymce/ui/Selector",
  18822. "tinymce/ui/Factory",
  18823. "tinymce/ui/KeyboardNavigation",
  18824. "tinymce/util/Tools",
  18825. "tinymce/ui/DomUtils"
  18826. ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
  18827. "use strict";
  18828. var selectorCache = {};
  18829. return Control.extend({
  18830. layout: '',
  18831. innerClass: 'container-inner',
  18832. /**
  18833. * Constructs a new control instance with the specified settings.
  18834. *
  18835. * @constructor
  18836. * @param {Object} settings Name/value object with settings.
  18837. * @setting {Array} items Items to add to container in JSON format or control instances.
  18838. * @setting {String} layout Layout manager by name to use.
  18839. * @setting {Object} defaults Default settings to apply to all items.
  18840. */
  18841. init: function(settings) {
  18842. var self = this;
  18843. self._super(settings);
  18844. settings = self.settings;
  18845. self._fixed = settings.fixed;
  18846. self._items = new Collection();
  18847. if (self.isRtl()) {
  18848. self.addClass('rtl');
  18849. }
  18850. self.addClass('container');
  18851. self.addClass('container-body', 'body');
  18852. if (settings.containerCls) {
  18853. self.addClass(settings.containerCls);
  18854. }
  18855. self._layout = Factory.create((settings.layout || self.layout) + 'layout');
  18856. if (self.settings.items) {
  18857. self.add(self.settings.items);
  18858. }
  18859. // TODO: Fix this!
  18860. self._hasBody = true;
  18861. },
  18862. /**
  18863. * Returns a collection of child items that the container currently have.
  18864. *
  18865. * @method items
  18866. * @return {tinymce.ui.Collection} Control collection direct child controls.
  18867. */
  18868. items: function() {
  18869. return this._items;
  18870. },
  18871. /**
  18872. * Find child controls by selector.
  18873. *
  18874. * @method find
  18875. * @param {String} selector Selector CSS pattern to find children by.
  18876. * @return {tinymce.ui.Collection} Control collection with child controls.
  18877. */
  18878. find: function(selector) {
  18879. selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
  18880. return selector.find(this);
  18881. },
  18882. /**
  18883. * Adds one or many items to the current container. This will create instances of
  18884. * the object representations if needed.
  18885. *
  18886. * @method add
  18887. * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
  18888. * @return {tinymce.ui.Collection} Current collection control.
  18889. */
  18890. add: function(items) {
  18891. var self = this;
  18892. self.items().add(self.create(items)).parent(self);
  18893. return self;
  18894. },
  18895. /**
  18896. * Focuses the current container instance. This will look
  18897. * for the first control in the container and focus that.
  18898. *
  18899. * @method focus
  18900. * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
  18901. * @return {tinymce.ui.Collection} Current instance.
  18902. */
  18903. focus: function(keyboard) {
  18904. var self = this, focusCtrl, keyboardNav, items;
  18905. if (keyboard) {
  18906. keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
  18907. if (keyboardNav) {
  18908. keyboardNav.focusFirst(self);
  18909. return;
  18910. }
  18911. }
  18912. items = self.find('*');
  18913. // TODO: Figure out a better way to auto focus alert dialog buttons
  18914. if (self.statusbar) {
  18915. items.add(self.statusbar.items());
  18916. }
  18917. items.each(function(ctrl) {
  18918. if (ctrl.settings.autofocus) {
  18919. focusCtrl = null;
  18920. return false;
  18921. }
  18922. if (ctrl.canFocus) {
  18923. focusCtrl = focusCtrl || ctrl;
  18924. }
  18925. });
  18926. if (focusCtrl) {
  18927. focusCtrl.focus();
  18928. }
  18929. return self;
  18930. },
  18931. /**
  18932. * Replaces the specified child control with a new control.
  18933. *
  18934. * @method replace
  18935. * @param {tinymce.ui.Control} oldItem Old item to be replaced.
  18936. * @param {tinymce.ui.Control} newItem New item to be inserted.
  18937. */
  18938. replace: function(oldItem, newItem) {
  18939. var ctrlElm, items = this.items(), i = items.length;
  18940. // Replace the item in collection
  18941. while (i--) {
  18942. if (items[i] === oldItem) {
  18943. items[i] = newItem;
  18944. break;
  18945. }
  18946. }
  18947. if (i >= 0) {
  18948. // Remove new item from DOM
  18949. ctrlElm = newItem.getEl();
  18950. if (ctrlElm) {
  18951. ctrlElm.parentNode.removeChild(ctrlElm);
  18952. }
  18953. // Remove old item from DOM
  18954. ctrlElm = oldItem.getEl();
  18955. if (ctrlElm) {
  18956. ctrlElm.parentNode.removeChild(ctrlElm);
  18957. }
  18958. }
  18959. // Adopt the item
  18960. newItem.parent(this);
  18961. },
  18962. /**
  18963. * Creates the specified items. If any of the items is plain JSON style objects
  18964. * it will convert these into real tinymce.ui.Control instances.
  18965. *
  18966. * @method create
  18967. * @param {Array} items Array of items to convert into control instances.
  18968. * @return {Array} Array with control instances.
  18969. */
  18970. create: function(items) {
  18971. var self = this, settings, ctrlItems = [];
  18972. // Non array structure, then force it into an array
  18973. if (!Tools.isArray(items)) {
  18974. items = [items];
  18975. }
  18976. // Add default type to each child control
  18977. Tools.each(items, function(item) {
  18978. if (item) {
  18979. // Construct item if needed
  18980. if (!(item instanceof Control)) {
  18981. // Name only then convert it to an object
  18982. if (typeof(item) == "string") {
  18983. item = {type: item};
  18984. }
  18985. // Create control instance based on input settings and default settings
  18986. settings = Tools.extend({}, self.settings.defaults, item);
  18987. item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
  18988. (settings.defaults ? settings.defaults.type : null);
  18989. item = Factory.create(settings);
  18990. }
  18991. ctrlItems.push(item);
  18992. }
  18993. });
  18994. return ctrlItems;
  18995. },
  18996. /**
  18997. * Renders new control instances.
  18998. *
  18999. * @private
  19000. */
  19001. renderNew: function() {
  19002. var self = this;
  19003. // Render any new items
  19004. self.items().each(function(ctrl, index) {
  19005. var containerElm, fragment;
  19006. ctrl.parent(self);
  19007. if (!ctrl._rendered) {
  19008. containerElm = self.getEl('body');
  19009. fragment = DomUtils.createFragment(ctrl.renderHtml());
  19010. // Insert or append the item
  19011. if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
  19012. containerElm.insertBefore(fragment, containerElm.childNodes[index]);
  19013. } else {
  19014. containerElm.appendChild(fragment);
  19015. }
  19016. ctrl.postRender();
  19017. }
  19018. });
  19019. self._layout.applyClasses(self);
  19020. self._lastRect = null;
  19021. return self;
  19022. },
  19023. /**
  19024. * Appends new instances to the current container.
  19025. *
  19026. * @method append
  19027. * @param {Array/tinymce.ui.Collection} items Array if controls to append.
  19028. * @return {tinymce.ui.Container} Current container instance.
  19029. */
  19030. append: function(items) {
  19031. return this.add(items).renderNew();
  19032. },
  19033. /**
  19034. * Prepends new instances to the current container.
  19035. *
  19036. * @method prepend
  19037. * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
  19038. * @return {tinymce.ui.Container} Current container instance.
  19039. */
  19040. prepend: function(items) {
  19041. var self = this;
  19042. self.items().set(self.create(items).concat(self.items().toArray()));
  19043. return self.renderNew();
  19044. },
  19045. /**
  19046. * Inserts an control at a specific index.
  19047. *
  19048. * @method insert
  19049. * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
  19050. * @param {Number} index Index to insert controls at.
  19051. * @param {Boolean} [before=false] Inserts controls before the index.
  19052. */
  19053. insert: function(items, index, before) {
  19054. var self = this, curItems, beforeItems, afterItems;
  19055. items = self.create(items);
  19056. curItems = self.items();
  19057. if (!before && index < curItems.length - 1) {
  19058. index += 1;
  19059. }
  19060. if (index >= 0 && index < curItems.length) {
  19061. beforeItems = curItems.slice(0, index).toArray();
  19062. afterItems = curItems.slice(index).toArray();
  19063. curItems.set(beforeItems.concat(items, afterItems));
  19064. }
  19065. return self.renderNew();
  19066. },
  19067. /**
  19068. * Populates the form fields from the specified JSON data object.
  19069. *
  19070. * Control items in the form that matches the data will have it's value set.
  19071. *
  19072. * @method fromJSON
  19073. * @param {Object} data JSON data object to set control values by.
  19074. * @return {tinymce.ui.Container} Current form instance.
  19075. */
  19076. fromJSON: function(data) {
  19077. var self = this;
  19078. for (var name in data) {
  19079. self.find('#' + name).value(data[name]);
  19080. }
  19081. return self;
  19082. },
  19083. /**
  19084. * Serializes the form into a JSON object by getting all items
  19085. * that has a name and a value.
  19086. *
  19087. * @method toJSON
  19088. * @return {Object} JSON object with form data.
  19089. */
  19090. toJSON: function() {
  19091. var self = this, data = {};
  19092. self.find('*').each(function(ctrl) {
  19093. var name = ctrl.name(), value = ctrl.value();
  19094. if (name && typeof(value) != "undefined") {
  19095. data[name] = value;
  19096. }
  19097. });
  19098. return data;
  19099. },
  19100. preRender: function() {
  19101. },
  19102. /**
  19103. * Renders the control as a HTML string.
  19104. *
  19105. * @method renderHtml
  19106. * @return {String} HTML representing the control.
  19107. */
  19108. renderHtml: function() {
  19109. var self = this, layout = self._layout, role = this.settings.role;
  19110. self.preRender();
  19111. layout.preRender(self);
  19112. return (
  19113. '<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
  19114. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  19115. (self.settings.html || '') + layout.renderHtml(self) +
  19116. '</div>' +
  19117. '</div>'
  19118. );
  19119. },
  19120. /**
  19121. * Post render method. Called after the control has been rendered to the target.
  19122. *
  19123. * @method postRender
  19124. * @return {tinymce.ui.Container} Current combobox instance.
  19125. */
  19126. postRender: function() {
  19127. var self = this, box;
  19128. self.items().exec('postRender');
  19129. self._super();
  19130. self._layout.postRender(self);
  19131. self._rendered = true;
  19132. if (self.settings.style) {
  19133. DomUtils.css(self.getEl(), self.settings.style);
  19134. }
  19135. if (self.settings.border) {
  19136. box = self.borderBox();
  19137. DomUtils.css(self.getEl(), {
  19138. 'border-top-width': box.top,
  19139. 'border-right-width': box.right,
  19140. 'border-bottom-width': box.bottom,
  19141. 'border-left-width': box.left
  19142. });
  19143. }
  19144. if (!self.parent()) {
  19145. self.keyboardNav = new KeyboardNavigation({
  19146. root: self
  19147. });
  19148. }
  19149. return self;
  19150. },
  19151. /**
  19152. * Initializes the current controls layout rect.
  19153. * This will be executed by the layout managers to determine the
  19154. * default minWidth/minHeight etc.
  19155. *
  19156. * @method initLayoutRect
  19157. * @return {Object} Layout rect instance.
  19158. */
  19159. initLayoutRect: function() {
  19160. var self = this, layoutRect = self._super();
  19161. // Recalc container size by asking layout manager
  19162. self._layout.recalc(self);
  19163. return layoutRect;
  19164. },
  19165. /**
  19166. * Recalculates the positions of the controls in the current container.
  19167. * This is invoked by the reflow method and shouldn't be called directly.
  19168. *
  19169. * @method recalc
  19170. */
  19171. recalc: function() {
  19172. var self = this, rect = self._layoutRect, lastRect = self._lastRect;
  19173. if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
  19174. self._layout.recalc(self);
  19175. rect = self.layoutRect();
  19176. self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
  19177. return true;
  19178. }
  19179. },
  19180. /**
  19181. * Reflows the current container and it's children and possible parents.
  19182. * This should be used after you for example append children to the current control so
  19183. * that the layout managers know that they need to reposition everything.
  19184. *
  19185. * @example
  19186. * container.append({type: 'button', text: 'My button'}).reflow();
  19187. *
  19188. * @method reflow
  19189. * @return {tinymce.ui.Container} Current container instance.
  19190. */
  19191. reflow: function() {
  19192. var i;
  19193. if (this.visible()) {
  19194. Control.repaintControls = [];
  19195. Control.repaintControls.map = {};
  19196. this.recalc();
  19197. i = Control.repaintControls.length;
  19198. while (i--) {
  19199. Control.repaintControls[i].repaint();
  19200. }
  19201. // TODO: Fix me!
  19202. if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
  19203. this.repaint();
  19204. }
  19205. Control.repaintControls = [];
  19206. }
  19207. return this;
  19208. }
  19209. });
  19210. });
  19211. // Included from: js/tinymce/classes/ui/DragHelper.js
  19212. /**
  19213. * DragHelper.js
  19214. *
  19215. * Copyright, Moxiecode Systems AB
  19216. * Released under LGPL License.
  19217. *
  19218. * License: http://www.tinymce.com/license
  19219. * Contributing: http://www.tinymce.com/contributing
  19220. */
  19221. /**
  19222. * Drag/drop helper class.
  19223. *
  19224. * @example
  19225. * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
  19226. * start: function(evt) {
  19227. * },
  19228. *
  19229. * drag: function(evt) {
  19230. * },
  19231. *
  19232. * end: function(evt) {
  19233. * }
  19234. * });
  19235. *
  19236. * @class tinymce.ui.DragHelper
  19237. */
  19238. define("tinymce/ui/DragHelper", [
  19239. "tinymce/ui/DomUtils"
  19240. ], function(DomUtils) {
  19241. "use strict";
  19242. function getDocumentSize() {
  19243. var doc = document, documentElement, body, scrollWidth, clientWidth;
  19244. var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
  19245. documentElement = doc.documentElement;
  19246. body = doc.body;
  19247. scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
  19248. clientWidth = max(documentElement.clientWidth, body.clientWidth);
  19249. offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
  19250. scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
  19251. clientHeight = max(documentElement.clientHeight, body.clientHeight);
  19252. offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
  19253. return {
  19254. width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
  19255. height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
  19256. };
  19257. }
  19258. return function(id, settings) {
  19259. var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;
  19260. settings = settings || {};
  19261. function getHandleElm() {
  19262. return doc.getElementById(settings.handle || id);
  19263. }
  19264. start = function(e) {
  19265. var docSize = getDocumentSize(), handleElm, cursor;
  19266. e.preventDefault();
  19267. downButton = e.button;
  19268. handleElm = getHandleElm();
  19269. startX = e.screenX;
  19270. startY = e.screenY;
  19271. // Grab cursor from handle
  19272. if (window.getComputedStyle) {
  19273. cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
  19274. } else {
  19275. cursor = handleElm.runtimeStyle.cursor;
  19276. }
  19277. // Create event overlay and add it to document
  19278. eventOverlayElm = doc.createElement('div');
  19279. DomUtils.css(eventOverlayElm, {
  19280. position: "absolute",
  19281. top: 0, left: 0,
  19282. width: docSize.width,
  19283. height: docSize.height,
  19284. zIndex: 0x7FFFFFFF,
  19285. opacity: 0.0001,
  19286. background: 'red',
  19287. cursor: cursor
  19288. });
  19289. doc.body.appendChild(eventOverlayElm);
  19290. // Bind mouse events
  19291. DomUtils.on(doc, 'mousemove', drag);
  19292. DomUtils.on(doc, 'mouseup', stop);
  19293. // Begin drag
  19294. settings.start(e);
  19295. };
  19296. drag = function(e) {
  19297. if (e.button !== downButton) {
  19298. return stop(e);
  19299. }
  19300. e.deltaX = e.screenX - startX;
  19301. e.deltaY = e.screenY - startY;
  19302. e.preventDefault();
  19303. settings.drag(e);
  19304. };
  19305. stop = function(e) {
  19306. DomUtils.off(doc, 'mousemove', drag);
  19307. DomUtils.off(doc, 'mouseup', stop);
  19308. eventOverlayElm.parentNode.removeChild(eventOverlayElm);
  19309. if (settings.stop) {
  19310. settings.stop(e);
  19311. }
  19312. };
  19313. /**
  19314. * Destroys the drag/drop helper instance.
  19315. *
  19316. * @method destroy
  19317. */
  19318. this.destroy = function() {
  19319. DomUtils.off(getHandleElm());
  19320. };
  19321. DomUtils.on(getHandleElm(), 'mousedown', start);
  19322. };
  19323. });
  19324. // Included from: js/tinymce/classes/ui/Scrollable.js
  19325. /**
  19326. * Scrollable.js
  19327. *
  19328. * Copyright, Moxiecode Systems AB
  19329. * Released under LGPL License.
  19330. *
  19331. * License: http://www.tinymce.com/license
  19332. * Contributing: http://www.tinymce.com/contributing
  19333. */
  19334. /**
  19335. * This mixin makes controls scrollable using custom scrollbars.
  19336. *
  19337. * @-x-less Scrollable.less
  19338. * @mixin tinymce.ui.Scrollable
  19339. */
  19340. define("tinymce/ui/Scrollable", [
  19341. "tinymce/ui/DomUtils",
  19342. "tinymce/ui/DragHelper"
  19343. ], function(DomUtils, DragHelper) {
  19344. "use strict";
  19345. return {
  19346. init: function() {
  19347. var self = this;
  19348. self.on('repaint', self.renderScroll);
  19349. },
  19350. renderScroll: function() {
  19351. var self = this, margin = 2;
  19352. function repaintScroll() {
  19353. var hasScrollH, hasScrollV, bodyElm;
  19354. function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
  19355. var containerElm, scrollBarElm, scrollThumbElm;
  19356. var containerSize, scrollSize, ratio, rect;
  19357. var posNameLower, sizeNameLower;
  19358. scrollBarElm = self.getEl('scroll' + axisName);
  19359. if (scrollBarElm) {
  19360. posNameLower = posName.toLowerCase();
  19361. sizeNameLower = sizeName.toLowerCase();
  19362. if (self.getEl('absend')) {
  19363. DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
  19364. }
  19365. if (!hasScroll) {
  19366. DomUtils.css(scrollBarElm, 'display', 'none');
  19367. return;
  19368. }
  19369. DomUtils.css(scrollBarElm, 'display', 'block');
  19370. containerElm = self.getEl('body');
  19371. scrollThumbElm = self.getEl('scroll' + axisName + "t");
  19372. containerSize = containerElm["client" + sizeName] - (margin * 2);
  19373. containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
  19374. scrollSize = containerElm["scroll" + sizeName];
  19375. ratio = containerSize / scrollSize;
  19376. rect = {};
  19377. rect[posNameLower] = containerElm["offset" + posName] + margin;
  19378. rect[sizeNameLower] = containerSize;
  19379. DomUtils.css(scrollBarElm, rect);
  19380. rect = {};
  19381. rect[posNameLower] = containerElm["scroll" + posName] * ratio;
  19382. rect[sizeNameLower] = containerSize * ratio;
  19383. DomUtils.css(scrollThumbElm, rect);
  19384. }
  19385. }
  19386. bodyElm = self.getEl('body');
  19387. hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
  19388. hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
  19389. repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
  19390. repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
  19391. }
  19392. function addScroll() {
  19393. function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
  19394. var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
  19395. self.getEl().appendChild(DomUtils.createFragment(
  19396. '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
  19397. '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
  19398. '</div>'
  19399. ));
  19400. self.draghelper = new DragHelper(axisId + 't', {
  19401. start: function() {
  19402. scrollStart = self.getEl('body')["scroll" + posName];
  19403. DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
  19404. },
  19405. drag: function(e) {
  19406. var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
  19407. hasScrollH = layoutRect.contentW > layoutRect.innerW;
  19408. hasScrollV = layoutRect.contentH > layoutRect.innerH;
  19409. containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
  19410. containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
  19411. ratio = containerSize / self.getEl('body')["scroll" + sizeName];
  19412. self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
  19413. },
  19414. stop: function() {
  19415. DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
  19416. }
  19417. });
  19418. /*
  19419. self.on('click', function(e) {
  19420. if (e.target.id == self._id + '-scrollv') {
  19421. }
  19422. });*/
  19423. }
  19424. self.addClass('scroll');
  19425. addScrollAxis("v", "Top", "Height", "Y", "Width");
  19426. addScrollAxis("h", "Left", "Width", "X", "Height");
  19427. }
  19428. if (self.settings.autoScroll) {
  19429. if (!self._hasScroll) {
  19430. self._hasScroll = true;
  19431. addScroll();
  19432. self.on('wheel', function(e) {
  19433. var bodyEl = self.getEl('body');
  19434. bodyEl.scrollLeft += (e.deltaX || 0) * 10;
  19435. bodyEl.scrollTop += e.deltaY * 10;
  19436. repaintScroll();
  19437. });
  19438. DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
  19439. }
  19440. repaintScroll();
  19441. }
  19442. }
  19443. };
  19444. });
  19445. // Included from: js/tinymce/classes/ui/Panel.js
  19446. /**
  19447. * Panel.js
  19448. *
  19449. * Copyright, Moxiecode Systems AB
  19450. * Released under LGPL License.
  19451. *
  19452. * License: http://www.tinymce.com/license
  19453. * Contributing: http://www.tinymce.com/contributing
  19454. */
  19455. /**
  19456. * Creates a new panel.
  19457. *
  19458. * @-x-less Panel.less
  19459. * @class tinymce.ui.Panel
  19460. * @extends tinymce.ui.Container
  19461. * @mixes tinymce.ui.Scrollable
  19462. */
  19463. define("tinymce/ui/Panel", [
  19464. "tinymce/ui/Container",
  19465. "tinymce/ui/Scrollable"
  19466. ], function(Container, Scrollable) {
  19467. "use strict";
  19468. return Container.extend({
  19469. Defaults: {
  19470. layout: 'fit',
  19471. containerCls: 'panel'
  19472. },
  19473. Mixins: [Scrollable],
  19474. /**
  19475. * Renders the control as a HTML string.
  19476. *
  19477. * @method renderHtml
  19478. * @return {String} HTML representing the control.
  19479. */
  19480. renderHtml: function() {
  19481. var self = this, layout = self._layout, innerHtml = self.settings.html;
  19482. self.preRender();
  19483. layout.preRender(self);
  19484. if (typeof(innerHtml) == "undefined") {
  19485. innerHtml = (
  19486. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  19487. layout.renderHtml(self) +
  19488. '</div>'
  19489. );
  19490. } else {
  19491. if (typeof(innerHtml) == 'function') {
  19492. innerHtml = innerHtml.call(self);
  19493. }
  19494. self._hasBody = false;
  19495. }
  19496. return (
  19497. '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
  19498. (self._preBodyHtml || '') +
  19499. innerHtml +
  19500. '</div>'
  19501. );
  19502. }
  19503. });
  19504. });
  19505. // Included from: js/tinymce/classes/ui/Movable.js
  19506. /**
  19507. * Movable.js
  19508. *
  19509. * Copyright, Moxiecode Systems AB
  19510. * Released under LGPL License.
  19511. *
  19512. * License: http://www.tinymce.com/license
  19513. * Contributing: http://www.tinymce.com/contributing
  19514. */
  19515. /**
  19516. * Movable mixin. Makes controls movable absolute and relative to other elements.
  19517. *
  19518. * @mixin tinymce.ui.Movable
  19519. */
  19520. define("tinymce/ui/Movable", [
  19521. "tinymce/ui/DomUtils"
  19522. ], function(DomUtils) {
  19523. "use strict";
  19524. function calculateRelativePosition(ctrl, targetElm, rel) {
  19525. var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
  19526. viewport = DomUtils.getViewPort();
  19527. // Get pos of target
  19528. pos = DomUtils.getPos(targetElm);
  19529. x = pos.x;
  19530. y = pos.y;
  19531. if (ctrl._fixed) {
  19532. x -= viewport.x;
  19533. y -= viewport.y;
  19534. }
  19535. // Get size of self
  19536. ctrlElm = ctrl.getEl();
  19537. size = DomUtils.getSize(ctrlElm);
  19538. selfW = size.width;
  19539. selfH = size.height;
  19540. // Get size of target
  19541. size = DomUtils.getSize(targetElm);
  19542. targetW = size.width;
  19543. targetH = size.height;
  19544. // Parse align string
  19545. rel = (rel || '').split('');
  19546. // Target corners
  19547. if (rel[0] === 'b') {
  19548. y += targetH;
  19549. }
  19550. if (rel[1] === 'r') {
  19551. x += targetW;
  19552. }
  19553. if (rel[0] === 'c') {
  19554. y += Math.round(targetH / 2);
  19555. }
  19556. if (rel[1] === 'c') {
  19557. x += Math.round(targetW / 2);
  19558. }
  19559. // Self corners
  19560. if (rel[3] === 'b') {
  19561. y -= selfH;
  19562. }
  19563. if (rel[4] === 'r') {
  19564. x -= selfW;
  19565. }
  19566. if (rel[3] === 'c') {
  19567. y -= Math.round(selfH / 2);
  19568. }
  19569. if (rel[4] === 'c') {
  19570. x -= Math.round(selfW / 2);
  19571. }
  19572. return {
  19573. x: x,
  19574. y: y,
  19575. w: selfW,
  19576. h: selfH
  19577. };
  19578. }
  19579. return {
  19580. /**
  19581. * Tests various positions to get the most suitable one.
  19582. *
  19583. * @method testMoveRel
  19584. * @param {DOMElement} elm Element to position against.
  19585. * @param {Array} rels Array with relative positions.
  19586. * @return {String} Best suitable relative position.
  19587. */
  19588. testMoveRel: function(elm, rels) {
  19589. var viewPortRect = DomUtils.getViewPort();
  19590. for (var i = 0; i < rels.length; i++) {
  19591. var pos = calculateRelativePosition(this, elm, rels[i]);
  19592. if (this._fixed) {
  19593. if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
  19594. return rels[i];
  19595. }
  19596. } else {
  19597. if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
  19598. pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
  19599. return rels[i];
  19600. }
  19601. }
  19602. }
  19603. return rels[0];
  19604. },
  19605. /**
  19606. * Move relative to the specified element.
  19607. *
  19608. * @method moveRel
  19609. * @param {Element} elm Element to move relative to.
  19610. * @param {String} rel Relative mode. For example: br-tl.
  19611. * @return {tinymce.ui.Control} Current control instance.
  19612. */
  19613. moveRel: function(elm, rel) {
  19614. if (typeof(rel) != 'string') {
  19615. rel = this.testMoveRel(elm, rel);
  19616. }
  19617. var pos = calculateRelativePosition(this, elm, rel);
  19618. return this.moveTo(pos.x, pos.y);
  19619. },
  19620. /**
  19621. * Move by a relative x, y values.
  19622. *
  19623. * @method moveBy
  19624. * @param {Number} dx Relative x position.
  19625. * @param {Number} dy Relative y position.
  19626. * @return {tinymce.ui.Control} Current control instance.
  19627. */
  19628. moveBy: function(dx, dy) {
  19629. var self = this, rect = self.layoutRect();
  19630. self.moveTo(rect.x + dx, rect.y + dy);
  19631. return self;
  19632. },
  19633. /**
  19634. * Move to absolute position.
  19635. *
  19636. * @method moveTo
  19637. * @param {Number} x Absolute x position.
  19638. * @param {Number} y Absolute y position.
  19639. * @return {tinymce.ui.Control} Current control instance.
  19640. */
  19641. moveTo: function(x, y) {
  19642. var self = this;
  19643. // TODO: Move this to some global class
  19644. function contrain(value, max, size) {
  19645. if (value < 0) {
  19646. return 0;
  19647. }
  19648. if (value + size > max) {
  19649. value = max - size;
  19650. return value < 0 ? 0 : value;
  19651. }
  19652. return value;
  19653. }
  19654. if (self.settings.constrainToViewport) {
  19655. var viewPortRect = DomUtils.getViewPort(window);
  19656. var layoutRect = self.layoutRect();
  19657. x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
  19658. y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
  19659. }
  19660. if (self._rendered) {
  19661. self.layoutRect({x: x, y: y}).repaint();
  19662. } else {
  19663. self.settings.x = x;
  19664. self.settings.y = y;
  19665. }
  19666. self.fire('move', {x: x, y: y});
  19667. return self;
  19668. }
  19669. };
  19670. });
  19671. // Included from: js/tinymce/classes/ui/Resizable.js
  19672. /**
  19673. * Resizable.js
  19674. *
  19675. * Copyright, Moxiecode Systems AB
  19676. * Released under LGPL License.
  19677. *
  19678. * License: http://www.tinymce.com/license
  19679. * Contributing: http://www.tinymce.com/contributing
  19680. */
  19681. /**
  19682. * Resizable mixin. Enables controls to be resized.
  19683. *
  19684. * @mixin tinymce.ui.Resizable
  19685. */
  19686. define("tinymce/ui/Resizable", [
  19687. "tinymce/ui/DomUtils"
  19688. ], function(DomUtils) {
  19689. "use strict";
  19690. return {
  19691. /**
  19692. * Resizes the control to contents.
  19693. *
  19694. * @method resizeToContent
  19695. */
  19696. resizeToContent: function() {
  19697. this._layoutRect.autoResize = true;
  19698. this._lastRect = null;
  19699. this.reflow();
  19700. },
  19701. /**
  19702. * Resizes the control to a specific width/height.
  19703. *
  19704. * @method resizeTo
  19705. * @param {Number} w Control width.
  19706. * @param {Number} h Control height.
  19707. * @return {tinymce.ui.Control} Current control instance.
  19708. */
  19709. resizeTo: function(w, h) {
  19710. // TODO: Fix hack
  19711. if (w <= 1 || h <= 1) {
  19712. var rect = DomUtils.getWindowSize();
  19713. w = w <= 1 ? w * rect.w : w;
  19714. h = h <= 1 ? h * rect.h : h;
  19715. }
  19716. this._layoutRect.autoResize = false;
  19717. return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
  19718. },
  19719. /**
  19720. * Resizes the control to a specific relative width/height.
  19721. *
  19722. * @method resizeBy
  19723. * @param {Number} dw Relative control width.
  19724. * @param {Number} dh Relative control height.
  19725. * @return {tinymce.ui.Control} Current control instance.
  19726. */
  19727. resizeBy: function(dw, dh) {
  19728. var self = this, rect = self.layoutRect();
  19729. return self.resizeTo(rect.w + dw, rect.h + dh);
  19730. }
  19731. };
  19732. });
  19733. // Included from: js/tinymce/classes/ui/FloatPanel.js
  19734. /**
  19735. * FloatPanel.js
  19736. *
  19737. * Copyright, Moxiecode Systems AB
  19738. * Released under LGPL License.
  19739. *
  19740. * License: http://www.tinymce.com/license
  19741. * Contributing: http://www.tinymce.com/contributing
  19742. */
  19743. /**
  19744. * This class creates a floating panel.
  19745. *
  19746. * @-x-less FloatPanel.less
  19747. * @class tinymce.ui.FloatPanel
  19748. * @extends tinymce.ui.Panel
  19749. * @mixes tinymce.ui.Movable
  19750. * @mixes tinymce.ui.Resizable
  19751. */
  19752. define("tinymce/ui/FloatPanel", [
  19753. "tinymce/ui/Panel",
  19754. "tinymce/ui/Movable",
  19755. "tinymce/ui/Resizable",
  19756. "tinymce/ui/DomUtils"
  19757. ], function(Panel, Movable, Resizable, DomUtils) {
  19758. "use strict";
  19759. var documentClickHandler, documentScrollHandler, visiblePanels = [];
  19760. var zOrder = [], hasModal;
  19761. var FloatPanel = Panel.extend({
  19762. Mixins: [Movable, Resizable],
  19763. /**
  19764. * Constructs a new control instance with the specified settings.
  19765. *
  19766. * @constructor
  19767. * @param {Object} settings Name/value object with settings.
  19768. * @setting {Boolean} autohide Automatically hide the panel.
  19769. */
  19770. init: function(settings) {
  19771. var self = this;
  19772. function reorder() {
  19773. var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
  19774. if (zOrder.length) {
  19775. for (i = 0; i < zOrder.length; i++) {
  19776. if (zOrder[i].modal) {
  19777. zIndex++;
  19778. topModal = zOrder[i];
  19779. }
  19780. zOrder[i].getEl().style.zIndex = zIndex;
  19781. zOrder[i].zIndex = zIndex;
  19782. zIndex++;
  19783. }
  19784. }
  19785. var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block');
  19786. if (topModal) {
  19787. DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
  19788. } else if (modalBlockEl) {
  19789. modalBlockEl.parentNode.removeChild(modalBlockEl);
  19790. hasModal = false;
  19791. }
  19792. FloatPanel.currentZIndex = zIndex;
  19793. }
  19794. function isChildOf(ctrl, parent) {
  19795. while (ctrl) {
  19796. if (ctrl == parent) {
  19797. return true;
  19798. }
  19799. ctrl = ctrl.parent();
  19800. }
  19801. }
  19802. /**
  19803. * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
  19804. * also reposition all child panels of the current panel.
  19805. */
  19806. function repositionPanel(panel) {
  19807. var scrollY = DomUtils.getViewPort().y;
  19808. function toggleFixedChildPanels(fixed, deltaY) {
  19809. var parent;
  19810. for (var i = 0; i < visiblePanels.length; i++) {
  19811. if (visiblePanels[i] != panel) {
  19812. parent = visiblePanels[i].parent();
  19813. while (parent && (parent = parent.parent())) {
  19814. if (parent == panel) {
  19815. visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
  19816. }
  19817. }
  19818. }
  19819. }
  19820. }
  19821. if (panel.settings.autofix) {
  19822. if (!panel._fixed) {
  19823. panel._autoFixY = panel.layoutRect().y;
  19824. if (panel._autoFixY < scrollY) {
  19825. panel.fixed(true).layoutRect({y: 0}).repaint();
  19826. toggleFixedChildPanels(true, scrollY - panel._autoFixY);
  19827. }
  19828. } else {
  19829. if (panel._autoFixY > scrollY) {
  19830. panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
  19831. toggleFixedChildPanels(false, panel._autoFixY - scrollY);
  19832. }
  19833. }
  19834. }
  19835. }
  19836. self._super(settings);
  19837. self._eventsRoot = self;
  19838. self.addClass('floatpanel');
  19839. // Hide floatpanes on click out side the root button
  19840. if (settings.autohide) {
  19841. if (!documentClickHandler) {
  19842. documentClickHandler = function(e) {
  19843. // Hide any float panel when a click is out side that float panel and the
  19844. // float panels direct parent for example a click on a menu button
  19845. var i = visiblePanels.length;
  19846. while (i--) {
  19847. var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
  19848. if (panel.settings.autohide) {
  19849. if (clickCtrl) {
  19850. if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
  19851. continue;
  19852. }
  19853. }
  19854. e = panel.fire('autohide', {target: e.target});
  19855. if (!e.isDefaultPrevented()) {
  19856. panel.hide();
  19857. }
  19858. }
  19859. }
  19860. };
  19861. DomUtils.on(document, 'click', documentClickHandler);
  19862. }
  19863. visiblePanels.push(self);
  19864. }
  19865. if (settings.autofix) {
  19866. if (!documentScrollHandler) {
  19867. documentScrollHandler = function() {
  19868. var i;
  19869. i = visiblePanels.length;
  19870. while (i--) {
  19871. repositionPanel(visiblePanels[i]);
  19872. }
  19873. };
  19874. DomUtils.on(window, 'scroll', documentScrollHandler);
  19875. }
  19876. self.on('move', function() {
  19877. repositionPanel(this);
  19878. });
  19879. }
  19880. self.on('postrender show', function(e) {
  19881. if (e.control == self) {
  19882. var modalBlockEl, prefix = self.classPrefix;
  19883. if (self.modal && !hasModal) {
  19884. modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
  19885. prefix + 'reset ' + prefix + 'fade"></div>');
  19886. modalBlockEl = modalBlockEl.firstChild;
  19887. self.getContainerElm().appendChild(modalBlockEl);
  19888. setTimeout(function() {
  19889. DomUtils.addClass(modalBlockEl, prefix + 'in');
  19890. DomUtils.addClass(self.getEl(), prefix + 'in');
  19891. }, 0);
  19892. hasModal = true;
  19893. }
  19894. zOrder.push(self);
  19895. reorder();
  19896. }
  19897. });
  19898. self.on('close hide', function(e) {
  19899. if (e.control == self) {
  19900. var i = zOrder.length;
  19901. while (i--) {
  19902. if (zOrder[i] === self) {
  19903. zOrder.splice(i, 1);
  19904. }
  19905. }
  19906. reorder();
  19907. }
  19908. });
  19909. self.on('show', function() {
  19910. self.parents().each(function(ctrl) {
  19911. if (ctrl._fixed) {
  19912. self.fixed(true);
  19913. return false;
  19914. }
  19915. });
  19916. });
  19917. if (settings.popover) {
  19918. self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
  19919. self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start');
  19920. }
  19921. },
  19922. fixed: function(state) {
  19923. var self = this;
  19924. if (self._fixed != state) {
  19925. if (self._rendered) {
  19926. var viewport = DomUtils.getViewPort();
  19927. if (state) {
  19928. self.layoutRect().y -= viewport.y;
  19929. } else {
  19930. self.layoutRect().y += viewport.y;
  19931. }
  19932. }
  19933. self.toggleClass('fixed', state);
  19934. self._fixed = state;
  19935. }
  19936. return self;
  19937. },
  19938. /**
  19939. * Shows the current float panel.
  19940. *
  19941. * @method show
  19942. * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
  19943. */
  19944. show: function() {
  19945. var self = this, i, state = self._super();
  19946. i = visiblePanels.length;
  19947. while (i--) {
  19948. if (visiblePanels[i] === self) {
  19949. break;
  19950. }
  19951. }
  19952. if (i === -1) {
  19953. visiblePanels.push(self);
  19954. }
  19955. return state;
  19956. },
  19957. /**
  19958. * Hides the current float panel.
  19959. *
  19960. * @method hide
  19961. * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
  19962. */
  19963. hide: function() {
  19964. removeVisiblePanel(this);
  19965. return this._super();
  19966. },
  19967. /**
  19968. * Hides all visible the float panels.
  19969. *
  19970. * @method hideAll
  19971. */
  19972. hideAll: function() {
  19973. FloatPanel.hideAll();
  19974. },
  19975. /**
  19976. * Closes the float panel. This will remove the float panel from page and fire the close event.
  19977. *
  19978. * @method close
  19979. */
  19980. close: function() {
  19981. var self = this;
  19982. self.fire('close');
  19983. return self.remove();
  19984. },
  19985. /**
  19986. * Removes the float panel from page.
  19987. *
  19988. * @method remove
  19989. */
  19990. remove: function() {
  19991. removeVisiblePanel(this);
  19992. this._super();
  19993. },
  19994. postRender: function() {
  19995. var self = this;
  19996. if (self.settings.bodyRole) {
  19997. this.getEl('body').setAttribute('role', self.settings.bodyRole);
  19998. }
  19999. return self._super();
  20000. }
  20001. });
  20002. /**
  20003. * Hides all visible the float panels.
  20004. *
  20005. * @static
  20006. * @method hideAll
  20007. */
  20008. FloatPanel.hideAll = function() {
  20009. var i = visiblePanels.length;
  20010. while (i--) {
  20011. var panel = visiblePanels[i];
  20012. if (panel && panel.settings.autohide) {
  20013. panel.hide();
  20014. visiblePanels.splice(i, 1);
  20015. }
  20016. }
  20017. };
  20018. function removeVisiblePanel(panel) {
  20019. var i;
  20020. i = visiblePanels.length;
  20021. while (i--) {
  20022. if (visiblePanels[i] === panel) {
  20023. visiblePanels.splice(i, 1);
  20024. }
  20025. }
  20026. i = zOrder.length;
  20027. while (i--) {
  20028. if (zOrder[i] === panel) {
  20029. zOrder.splice(i, 1);
  20030. }
  20031. }
  20032. }
  20033. return FloatPanel;
  20034. });
  20035. // Included from: js/tinymce/classes/ui/Window.js
  20036. /**
  20037. * Window.js
  20038. *
  20039. * Copyright, Moxiecode Systems AB
  20040. * Released under LGPL License.
  20041. *
  20042. * License: http://www.tinymce.com/license
  20043. * Contributing: http://www.tinymce.com/contributing
  20044. */
  20045. /**
  20046. * Creates a new window.
  20047. *
  20048. * @-x-less Window.less
  20049. * @class tinymce.ui.Window
  20050. * @extends tinymce.ui.FloatPanel
  20051. */
  20052. define("tinymce/ui/Window", [
  20053. "tinymce/ui/FloatPanel",
  20054. "tinymce/ui/Panel",
  20055. "tinymce/ui/DomUtils",
  20056. "tinymce/ui/DragHelper"
  20057. ], function(FloatPanel, Panel, DomUtils, DragHelper) {
  20058. "use strict";
  20059. var Window = FloatPanel.extend({
  20060. modal: true,
  20061. Defaults: {
  20062. border: 1,
  20063. layout: 'flex',
  20064. containerCls: 'panel',
  20065. role: 'dialog',
  20066. callbacks: {
  20067. submit: function() {
  20068. this.fire('submit', {data: this.toJSON()});
  20069. },
  20070. close: function() {
  20071. this.close();
  20072. }
  20073. }
  20074. },
  20075. /**
  20076. * Constructs a instance with the specified settings.
  20077. *
  20078. * @constructor
  20079. * @param {Object} settings Name/value object with settings.
  20080. */
  20081. init: function(settings) {
  20082. var self = this;
  20083. self._super(settings);
  20084. if (self.isRtl()) {
  20085. self.addClass('rtl');
  20086. }
  20087. self.addClass('window');
  20088. self._fixed = true;
  20089. // Create statusbar
  20090. if (settings.buttons) {
  20091. self.statusbar = new Panel({
  20092. layout: 'flex',
  20093. border: '1 0 0 0',
  20094. spacing: 3,
  20095. padding: 10,
  20096. align: 'center',
  20097. pack: self.isRtl() ? 'start' : 'end',
  20098. defaults: {
  20099. type: 'button'
  20100. },
  20101. items: settings.buttons
  20102. });
  20103. self.statusbar.addClass('foot');
  20104. self.statusbar.parent(self);
  20105. }
  20106. self.on('click', function(e) {
  20107. if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
  20108. self.close();
  20109. }
  20110. });
  20111. self.on('cancel', function() {
  20112. self.close();
  20113. });
  20114. self.aria('describedby', self.describedBy || self._id + '-none');
  20115. self.aria('label', settings.title);
  20116. self._fullscreen = false;
  20117. },
  20118. /**
  20119. * Recalculates the positions of the controls in the current container.
  20120. * This is invoked by the reflow method and shouldn't be called directly.
  20121. *
  20122. * @method recalc
  20123. */
  20124. recalc: function() {
  20125. var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
  20126. if (self._fullscreen) {
  20127. self.layoutRect(DomUtils.getWindowSize());
  20128. self.layoutRect().contentH = self.layoutRect().innerH;
  20129. }
  20130. self._super();
  20131. layoutRect = self.layoutRect();
  20132. // Resize window based on title width
  20133. if (self.settings.title && !self._fullscreen) {
  20134. width = layoutRect.headerW;
  20135. if (width > layoutRect.w) {
  20136. x = layoutRect.x - Math.max(0, width / 2);
  20137. self.layoutRect({w: width, x: x});
  20138. needsRecalc = true;
  20139. }
  20140. }
  20141. // Resize window based on statusbar width
  20142. if (statusbar) {
  20143. statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
  20144. width = statusbar.layoutRect().minW + layoutRect.deltaW;
  20145. if (width > layoutRect.w) {
  20146. x = layoutRect.x - Math.max(0, width - layoutRect.w);
  20147. self.layoutRect({w: width, x: x});
  20148. needsRecalc = true;
  20149. }
  20150. }
  20151. // Recalc body and disable auto resize
  20152. if (needsRecalc) {
  20153. self.recalc();
  20154. }
  20155. },
  20156. /**
  20157. * Initializes the current controls layout rect.
  20158. * This will be executed by the layout managers to determine the
  20159. * default minWidth/minHeight etc.
  20160. *
  20161. * @method initLayoutRect
  20162. * @return {Object} Layout rect instance.
  20163. */
  20164. initLayoutRect: function() {
  20165. var self = this, layoutRect = self._super(), deltaH = 0, headEl;
  20166. // Reserve vertical space for title
  20167. if (self.settings.title && !self._fullscreen) {
  20168. headEl = self.getEl('head');
  20169. var size = DomUtils.getSize(headEl);
  20170. layoutRect.headerW = size.width;
  20171. layoutRect.headerH = size.height;
  20172. deltaH += layoutRect.headerH;
  20173. }
  20174. // Reserve vertical space for statusbar
  20175. if (self.statusbar) {
  20176. deltaH += self.statusbar.layoutRect().h;
  20177. }
  20178. layoutRect.deltaH += deltaH;
  20179. layoutRect.minH += deltaH;
  20180. //layoutRect.innerH -= deltaH;
  20181. layoutRect.h += deltaH;
  20182. var rect = DomUtils.getWindowSize();
  20183. layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
  20184. layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);
  20185. return layoutRect;
  20186. },
  20187. /**
  20188. * Renders the control as a HTML string.
  20189. *
  20190. * @method renderHtml
  20191. * @return {String} HTML representing the control.
  20192. */
  20193. renderHtml: function() {
  20194. var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
  20195. var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
  20196. self.preRender();
  20197. layout.preRender(self);
  20198. if (settings.title) {
  20199. headerHtml = (
  20200. '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
  20201. '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
  20202. '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
  20203. '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
  20204. '</div>'
  20205. );
  20206. }
  20207. if (settings.url) {
  20208. html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
  20209. }
  20210. if (typeof(html) == "undefined") {
  20211. html = layout.renderHtml(self);
  20212. }
  20213. if (self.statusbar) {
  20214. footerHtml = self.statusbar.renderHtml();
  20215. }
  20216. return (
  20217. '<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
  20218. '<div class="' + self.classPrefix + 'reset" role="application">' +
  20219. headerHtml +
  20220. '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
  20221. html +
  20222. '</div>' +
  20223. footerHtml +
  20224. '</div>' +
  20225. '</div>'
  20226. );
  20227. },
  20228. /**
  20229. * Switches the window fullscreen mode.
  20230. *
  20231. * @method fullscreen
  20232. * @param {Boolean} state True/false state.
  20233. * @return {tinymce.ui.Window} Current window instance.
  20234. */
  20235. fullscreen: function(state) {
  20236. var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
  20237. if (state != self._fullscreen) {
  20238. DomUtils.on(window, 'resize', function() {
  20239. var time;
  20240. if (self._fullscreen) {
  20241. // Time the layout time if it's to slow use a timeout to not hog the CPU
  20242. if (!slowRendering) {
  20243. time = new Date().getTime();
  20244. var rect = DomUtils.getWindowSize();
  20245. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  20246. if ((new Date().getTime()) - time > 50) {
  20247. slowRendering = true;
  20248. }
  20249. } else {
  20250. if (!self._timer) {
  20251. self._timer = setTimeout(function() {
  20252. var rect = DomUtils.getWindowSize();
  20253. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  20254. self._timer = 0;
  20255. }, 50);
  20256. }
  20257. }
  20258. }
  20259. });
  20260. layoutRect = self.layoutRect();
  20261. self._fullscreen = state;
  20262. if (!state) {
  20263. self._borderBox = self.parseBox(self.settings.border);
  20264. self.getEl('head').style.display = '';
  20265. layoutRect.deltaH += layoutRect.headerH;
  20266. DomUtils.removeClass(documentElement, prefix + 'fullscreen');
  20267. DomUtils.removeClass(document.body, prefix + 'fullscreen');
  20268. self.removeClass('fullscreen');
  20269. self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
  20270. } else {
  20271. self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
  20272. self._borderBox = self.parseBox('0');
  20273. self.getEl('head').style.display = 'none';
  20274. layoutRect.deltaH -= layoutRect.headerH + 2;
  20275. DomUtils.addClass(documentElement, prefix + 'fullscreen');
  20276. DomUtils.addClass(document.body, prefix + 'fullscreen');
  20277. self.addClass('fullscreen');
  20278. var rect = DomUtils.getWindowSize();
  20279. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  20280. }
  20281. }
  20282. return self.reflow();
  20283. },
  20284. /**
  20285. * Called after the control has been rendered.
  20286. *
  20287. * @method postRender
  20288. */
  20289. postRender: function() {
  20290. var self = this, startPos;
  20291. setTimeout(function() {
  20292. self.addClass('in');
  20293. }, 0);
  20294. self._super();
  20295. if (self.statusbar) {
  20296. self.statusbar.postRender();
  20297. }
  20298. self.focus();
  20299. this.dragHelper = new DragHelper(self._id + '-dragh', {
  20300. start: function() {
  20301. startPos = {
  20302. x: self.layoutRect().x,
  20303. y: self.layoutRect().y
  20304. };
  20305. },
  20306. drag: function(e) {
  20307. self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
  20308. }
  20309. });
  20310. self.on('submit', function(e) {
  20311. if (!e.isDefaultPrevented()) {
  20312. self.close();
  20313. }
  20314. });
  20315. },
  20316. /**
  20317. * Fires a submit event with the serialized form.
  20318. *
  20319. * @method submit
  20320. * @return {Object} Event arguments object.
  20321. */
  20322. submit: function() {
  20323. return this.fire('submit', {data: this.toJSON()});
  20324. },
  20325. /**
  20326. * Removes the current control from DOM and from UI collections.
  20327. *
  20328. * @method remove
  20329. * @return {tinymce.ui.Control} Current control instance.
  20330. */
  20331. remove: function() {
  20332. var self = this, prefix = self.classPrefix;
  20333. self.dragHelper.destroy();
  20334. self._super();
  20335. if (self.statusbar) {
  20336. this.statusbar.remove();
  20337. }
  20338. if (self._fullscreen) {
  20339. DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
  20340. DomUtils.removeClass(document.body, prefix + 'fullscreen');
  20341. }
  20342. },
  20343. /**
  20344. * Returns the contentWindow object of the iframe if it exists.
  20345. *
  20346. * @method getContentWindow
  20347. * @return {Window} window object or null.
  20348. */
  20349. getContentWindow: function() {
  20350. var ifr = this.getEl().getElementsByTagName('iframe')[0];
  20351. return ifr ? ifr.contentWindow : null;
  20352. }
  20353. });
  20354. return Window;
  20355. });
  20356. // Included from: js/tinymce/classes/ui/MessageBox.js
  20357. /**
  20358. * MessageBox.js
  20359. *
  20360. * Copyright, Moxiecode Systems AB
  20361. * Released under LGPL License.
  20362. *
  20363. * License: http://www.tinymce.com/license
  20364. * Contributing: http://www.tinymce.com/contributing
  20365. */
  20366. /**
  20367. * This class is used to create MessageBoxes like alerts/confirms etc.
  20368. *
  20369. * @class tinymce.ui.Window
  20370. * @extends tinymce.ui.FloatPanel
  20371. */
  20372. define("tinymce/ui/MessageBox", [
  20373. "tinymce/ui/Window"
  20374. ], function(Window) {
  20375. "use strict";
  20376. var MessageBox = Window.extend({
  20377. /**
  20378. * Constructs a instance with the specified settings.
  20379. *
  20380. * @constructor
  20381. * @param {Object} settings Name/value object with settings.
  20382. */
  20383. init: function(settings) {
  20384. settings = {
  20385. border: 1,
  20386. padding: 20,
  20387. layout: 'flex',
  20388. pack: "center",
  20389. align: "center",
  20390. containerCls: 'panel',
  20391. autoScroll: true,
  20392. buttons: {type: "button", text: "Ok", action: "ok"},
  20393. items: {
  20394. type: "label",
  20395. multiline: true,
  20396. maxWidth: 500,
  20397. maxHeight: 200
  20398. }
  20399. };
  20400. this._super(settings);
  20401. },
  20402. Statics: {
  20403. /**
  20404. * Ok buttons constant.
  20405. *
  20406. * @static
  20407. * @final
  20408. * @field {Number} OK
  20409. */
  20410. OK: 1,
  20411. /**
  20412. * Ok/cancel buttons constant.
  20413. *
  20414. * @static
  20415. * @final
  20416. * @field {Number} OK_CANCEL
  20417. */
  20418. OK_CANCEL: 2,
  20419. /**
  20420. * yes/no buttons constant.
  20421. *
  20422. * @static
  20423. * @final
  20424. * @field {Number} YES_NO
  20425. */
  20426. YES_NO: 3,
  20427. /**
  20428. * yes/no/cancel buttons constant.
  20429. *
  20430. * @static
  20431. * @final
  20432. * @field {Number} YES_NO_CANCEL
  20433. */
  20434. YES_NO_CANCEL: 4,
  20435. /**
  20436. * Constructs a new message box and renders it to the body element.
  20437. *
  20438. * @static
  20439. * @method msgBox
  20440. * @param {Object} settings Name/value object with settings.
  20441. */
  20442. msgBox: function(settings) {
  20443. var buttons, callback = settings.callback || function() {};
  20444. switch (settings.buttons) {
  20445. case MessageBox.OK_CANCEL:
  20446. buttons = [
  20447. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20448. e.control.parents()[1].close();
  20449. callback(true);
  20450. }},
  20451. {type: "button", text: "Cancel", onClick: function(e) {
  20452. e.control.parents()[1].close();
  20453. callback(false);
  20454. }}
  20455. ];
  20456. break;
  20457. case MessageBox.YES_NO:
  20458. buttons = [
  20459. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20460. e.control.parents()[1].close();
  20461. callback(true);
  20462. }}
  20463. ];
  20464. break;
  20465. case MessageBox.YES_NO_CANCEL:
  20466. buttons = [
  20467. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20468. e.control.parents()[1].close();
  20469. }}
  20470. ];
  20471. break;
  20472. default:
  20473. buttons = [
  20474. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20475. e.control.parents()[1].close();
  20476. callback(true);
  20477. }}
  20478. ];
  20479. break;
  20480. }
  20481. return new Window({
  20482. padding: 20,
  20483. x: settings.x,
  20484. y: settings.y,
  20485. minWidth: 300,
  20486. minHeight: 100,
  20487. layout: "flex",
  20488. pack: "center",
  20489. align: "center",
  20490. buttons: buttons,
  20491. title: settings.title,
  20492. role: 'alertdialog',
  20493. items: {
  20494. type: "label",
  20495. multiline: true,
  20496. maxWidth: 500,
  20497. maxHeight: 200,
  20498. text: settings.text
  20499. },
  20500. onPostRender: function() {
  20501. this.aria('describedby', this.items()[0]._id);
  20502. },
  20503. onClose: settings.onClose,
  20504. onCancel: function() {
  20505. callback(false);
  20506. }
  20507. }).renderTo(document.body).reflow();
  20508. },
  20509. /**
  20510. * Creates a new alert dialog.
  20511. *
  20512. * @method alert
  20513. * @param {Object} settings Settings for the alert dialog.
  20514. * @param {function} [callback] Callback to execute when the user makes a choice.
  20515. */
  20516. alert: function(settings, callback) {
  20517. if (typeof(settings) == "string") {
  20518. settings = {text: settings};
  20519. }
  20520. settings.callback = callback;
  20521. return MessageBox.msgBox(settings);
  20522. },
  20523. /**
  20524. * Creates a new confirm dialog.
  20525. *
  20526. * @method confirm
  20527. * @param {Object} settings Settings for the confirm dialog.
  20528. * @param {function} [callback] Callback to execute when the user makes a choice.
  20529. */
  20530. confirm: function(settings, callback) {
  20531. if (typeof(settings) == "string") {
  20532. settings = {text: settings};
  20533. }
  20534. settings.callback = callback;
  20535. settings.buttons = MessageBox.OK_CANCEL;
  20536. return MessageBox.msgBox(settings);
  20537. }
  20538. }
  20539. });
  20540. return MessageBox;
  20541. });
  20542. // Included from: js/tinymce/classes/WindowManager.js
  20543. /**
  20544. * WindowManager.js
  20545. *
  20546. * Copyright, Moxiecode Systems AB
  20547. * Released under LGPL License.
  20548. *
  20549. * License: http://www.tinymce.com/license
  20550. * Contributing: http://www.tinymce.com/contributing
  20551. */
  20552. /**
  20553. * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
  20554. *
  20555. * @class tinymce.WindowManager
  20556. * @example
  20557. * // Opens a new dialog with the file.htm file and the size 320x240
  20558. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  20559. * tinymce.activeEditor.windowManager.open({
  20560. * url: 'file.htm',
  20561. * width: 320,
  20562. * height: 240
  20563. * }, {
  20564. * custom_param: 1
  20565. * });
  20566. *
  20567. * // Displays an alert box using the active editors window manager instance
  20568. * tinymce.activeEditor.windowManager.alert('Hello world!');
  20569. *
  20570. * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  20571. * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  20572. * if (s)
  20573. * tinymce.activeEditor.windowManager.alert("Ok");
  20574. * else
  20575. * tinymce.activeEditor.windowManager.alert("Cancel");
  20576. * });
  20577. */
  20578. define("tinymce/WindowManager", [
  20579. "tinymce/ui/Window",
  20580. "tinymce/ui/MessageBox"
  20581. ], function(Window, MessageBox) {
  20582. return function(editor) {
  20583. var self = this, windows = [];
  20584. function getTopMostWindow() {
  20585. if (windows.length) {
  20586. return windows[windows.length - 1];
  20587. }
  20588. }
  20589. self.windows = windows;
  20590. /**
  20591. * Opens a new window.
  20592. *
  20593. * @method open
  20594. * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
  20595. * @option {String} title Window title.
  20596. * @option {String} file URL of the file to open in the window.
  20597. * @option {Number} width Width in pixels.
  20598. * @option {Number} height Height in pixels.
  20599. * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
  20600. * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
  20601. * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
  20602. * larger than the popup size specified).
  20603. */
  20604. self.open = function(args, params) {
  20605. var win;
  20606. editor.editorManager.activeEditor = editor;
  20607. args.title = args.title || ' ';
  20608. // Handle URL
  20609. args.url = args.url || args.file; // Legacy
  20610. if (args.url) {
  20611. args.width = parseInt(args.width || 320, 10);
  20612. args.height = parseInt(args.height || 240, 10);
  20613. }
  20614. // Handle body
  20615. if (args.body) {
  20616. args.items = {
  20617. defaults: args.defaults,
  20618. type: args.bodyType || 'form',
  20619. items: args.body
  20620. };
  20621. }
  20622. if (!args.url && !args.buttons) {
  20623. args.buttons = [
  20624. {text: 'Ok', subtype: 'primary', onclick: function() {
  20625. win.find('form')[0].submit();
  20626. }},
  20627. {text: 'Cancel', onclick: function() {
  20628. win.close();
  20629. }}
  20630. ];
  20631. }
  20632. win = new Window(args);
  20633. windows.push(win);
  20634. win.on('close', function() {
  20635. var i = windows.length;
  20636. while (i--) {
  20637. if (windows[i] === win) {
  20638. windows.splice(i, 1);
  20639. }
  20640. }
  20641. editor.focus();
  20642. });
  20643. // Handle data
  20644. if (args.data) {
  20645. win.on('postRender', function() {
  20646. this.find('*').each(function(ctrl) {
  20647. var name = ctrl.name();
  20648. if (name in args.data) {
  20649. ctrl.value(args.data[name]);
  20650. }
  20651. });
  20652. });
  20653. }
  20654. // store args and parameters
  20655. win.features = args || {};
  20656. win.params = params || {};
  20657. // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
  20658. editor.nodeChanged();
  20659. return win.renderTo().reflow();
  20660. };
  20661. /**
  20662. * Creates a alert dialog. Please don't use the blocking behavior of this
  20663. * native version use the callback method instead then it can be extended.
  20664. *
  20665. * @method alert
  20666. * @param {String} message Text to display in the new alert dialog.
  20667. * @param {function} callback Callback function to be executed after the user has selected ok.
  20668. * @param {Object} scope Optional scope to execute the callback in.
  20669. * @example
  20670. * // Displays an alert box using the active editors window manager instance
  20671. * tinymce.activeEditor.windowManager.alert('Hello world!');
  20672. */
  20673. self.alert = function(message, callback, scope) {
  20674. MessageBox.alert(message, function() {
  20675. if (callback) {
  20676. callback.call(scope || this);
  20677. } else {
  20678. editor.focus();
  20679. }
  20680. });
  20681. };
  20682. /**
  20683. * Creates a confirm dialog. Please don't use the blocking behavior of this
  20684. * native version use the callback method instead then it can be extended.
  20685. *
  20686. * @method confirm
  20687. * @param {String} messageText to display in the new confirm dialog.
  20688. * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
  20689. * @param {Object} scope Optional scope to execute the callback in.
  20690. * @example
  20691. * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  20692. * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  20693. * if (s)
  20694. * tinymce.activeEditor.windowManager.alert("Ok");
  20695. * else
  20696. * tinymce.activeEditor.windowManager.alert("Cancel");
  20697. * });
  20698. */
  20699. self.confirm = function(message, callback, scope) {
  20700. MessageBox.confirm(message, function(state) {
  20701. callback.call(scope || this, state);
  20702. });
  20703. };
  20704. /**
  20705. * Closes the top most window.
  20706. *
  20707. * @method close
  20708. */
  20709. self.close = function() {
  20710. if (getTopMostWindow()) {
  20711. getTopMostWindow().close();
  20712. }
  20713. };
  20714. /**
  20715. * Returns the params of the last window open call. This can be used in iframe based
  20716. * dialog to get params passed from the tinymce plugin.
  20717. *
  20718. * @example
  20719. * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
  20720. *
  20721. * @method getParams
  20722. * @return {Object} Name/value object with parameters passed from windowManager.open call.
  20723. */
  20724. self.getParams = function() {
  20725. return getTopMostWindow() ? getTopMostWindow().params : null;
  20726. };
  20727. /**
  20728. * Sets the params of the last opened window.
  20729. *
  20730. * @method setParams
  20731. * @param {Object} params Params object to set for the last opened window.
  20732. */
  20733. self.setParams = function(params) {
  20734. if (getTopMostWindow()) {
  20735. getTopMostWindow().params = params;
  20736. }
  20737. };
  20738. /**
  20739. * Returns the currently opened window objects.
  20740. *
  20741. * @method getWindows
  20742. * @return {Array} Array of the currently opened windows.
  20743. */
  20744. self.getWindows = function() {
  20745. return windows;
  20746. };
  20747. };
  20748. });
  20749. // Included from: js/tinymce/classes/util/Quirks.js
  20750. /**
  20751. * Quirks.js
  20752. *
  20753. * Copyright, Moxiecode Systems AB
  20754. * Released under LGPL License.
  20755. *
  20756. * License: http://www.tinymce.com/license
  20757. * Contributing: http://www.tinymce.com/contributing
  20758. *
  20759. * @ignore-file
  20760. */
  20761. /**
  20762. * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
  20763. *
  20764. * @class tinymce.util.Quirks
  20765. */
  20766. define("tinymce/util/Quirks", [
  20767. "tinymce/util/VK",
  20768. "tinymce/dom/RangeUtils",
  20769. "tinymce/html/Node",
  20770. "tinymce/html/Entities",
  20771. "tinymce/Env",
  20772. "tinymce/util/Tools"
  20773. ], function(VK, RangeUtils, Node, Entities, Env, Tools) {
  20774. return function(editor) {
  20775. var each = Tools.each;
  20776. var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
  20777. settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
  20778. var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
  20779. /**
  20780. * Executes a command with a specific state this can be to enable/disable browser editing features.
  20781. */
  20782. function setEditorCommandState(cmd, state) {
  20783. try {
  20784. editor.getDoc().execCommand(cmd, false, state);
  20785. } catch (ex) {
  20786. // Ignore
  20787. }
  20788. }
  20789. /**
  20790. * Returns current IE document mode.
  20791. */
  20792. function getDocumentMode() {
  20793. var documentMode = editor.getDoc().documentMode;
  20794. return documentMode ? documentMode : 6;
  20795. }
  20796. /**
  20797. * Returns true/false if the event is prevented or not.
  20798. *
  20799. * @private
  20800. * @param {Event} e Event object.
  20801. * @return {Boolean} true/false if the event is prevented or not.
  20802. */
  20803. function isDefaultPrevented(e) {
  20804. return e.isDefaultPrevented();
  20805. }
  20806. /**
  20807. * Fixes a WebKit bug when deleting contents using backspace or delete key.
  20808. * WebKit will produce a span element if you delete across two block elements.
  20809. *
  20810. * Example:
  20811. * <h1>a</h1><p>|b</p>
  20812. *
  20813. * Will produce this on backspace:
  20814. * <h1>a<span style="<all runtime styles>">b</span></p>
  20815. *
  20816. * This fixes the backspace to produce:
  20817. * <h1>a|b</p>
  20818. *
  20819. * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
  20820. *
  20821. * This fixes the following delete scenarios:
  20822. * 1. Delete by pressing backspace key.
  20823. * 2. Delete by pressing delete key.
  20824. * 3. Delete by pressing backspace key with ctrl/cmd (Word delete).
  20825. * 4. Delete by pressing delete key with ctrl/cmd (Word delete).
  20826. * 5. Delete by drag/dropping contents inside the editor.
  20827. * 6. Delete by using Cut Ctrl+X/Cmd+X.
  20828. * 7. Delete by selecting contents and writing a character.'
  20829. *
  20830. * This code is a ugly hack since writing full custom delete logic for just this bug
  20831. * fix seemed like a huge task. I hope we can remove this before the year 2030.
  20832. */
  20833. function cleanupStylesWhenDeleting() {
  20834. var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
  20835. var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
  20836. // Add mini polyfill for older WebKits
  20837. // TODO: Remove this when old Safari versions gets updated
  20838. if (!MutationObserver) {
  20839. olderWebKit = true;
  20840. MutationObserver = function() {
  20841. var records = [], target;
  20842. function nodeInsert(e) {
  20843. var target = e.relatedNode || e.target;
  20844. records.push({target: target, addedNodes: [target]});
  20845. }
  20846. function attrModified(e) {
  20847. var target = e.relatedNode || e.target;
  20848. records.push({target: target, attributeName: e.attrName});
  20849. }
  20850. this.observe = function(node) {
  20851. target = node;
  20852. target.addEventListener('DOMSubtreeModified', nodeInsert, false);
  20853. target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
  20854. target.addEventListener('DOMNodeInserted', nodeInsert, false);
  20855. target.addEventListener('DOMAttrModified', attrModified, false);
  20856. };
  20857. this.disconnect = function() {
  20858. target.removeEventListener('DOMNodeInserted', nodeInsert);
  20859. target.removeEventListener('DOMAttrModified', attrModified);
  20860. target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
  20861. };
  20862. this.takeRecords = function() {
  20863. return records;
  20864. };
  20865. };
  20866. }
  20867. function customDelete(isForward) {
  20868. var mutationObserver = new MutationObserver(function() {});
  20869. Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
  20870. // Mark existing spans
  20871. if (elm.tagName == 'SPAN') {
  20872. elm.setAttribute('mce-data-marked', 1);
  20873. }
  20874. // Make sure all elements has a data-mce-style attribute
  20875. if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
  20876. editor.dom.setAttrib(elm, 'style', elm.getAttribute('style'));
  20877. }
  20878. });
  20879. // Observe added nodes and style attribute changes
  20880. mutationObserver.observe(editor.getDoc(), {
  20881. childList: true,
  20882. attributes: true,
  20883. subtree: true,
  20884. attributeFilter: ['style']
  20885. });
  20886. editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
  20887. var rng = editor.selection.getRng();
  20888. var caretElement = rng.startContainer.parentNode;
  20889. Tools.each(mutationObserver.takeRecords(), function(record) {
  20890. // Restore style attribute to previous value
  20891. if (record.attributeName == "style") {
  20892. var oldValue = record.target.getAttribute('data-mce-style');
  20893. if (oldValue) {
  20894. record.target.setAttribute("style", oldValue);
  20895. } else {
  20896. record.target.removeAttribute("style");
  20897. }
  20898. }
  20899. // Remove all spans that isn't maked and retain selection
  20900. Tools.each(record.addedNodes, function(node) {
  20901. if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
  20902. var offset, container;
  20903. if (node == caretElement) {
  20904. offset = rng.startOffset;
  20905. container = node.firstChild;
  20906. }
  20907. dom.remove(node, true);
  20908. if (container) {
  20909. rng.setStart(container, offset);
  20910. rng.setEnd(container, offset);
  20911. editor.selection.setRng(rng);
  20912. }
  20913. }
  20914. });
  20915. });
  20916. mutationObserver.disconnect();
  20917. // Remove any left over marks
  20918. Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
  20919. span.removeAttribute('mce-data-marked');
  20920. });
  20921. }
  20922. editor.on('keydown', function(e) {
  20923. var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e);
  20924. if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
  20925. var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
  20926. // Ignore non meta delete in the where there is text before/after the caret
  20927. if (!isMeta && rng.collapsed && container.nodeType == 3) {
  20928. if (isForward ? offset < container.data.length : offset > 0) {
  20929. return;
  20930. }
  20931. }
  20932. e.preventDefault();
  20933. if (isMeta) {
  20934. editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word");
  20935. }
  20936. customDelete(isForward);
  20937. }
  20938. });
  20939. editor.on('keypress', function(e) {
  20940. if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
  20941. e.preventDefault();
  20942. customDelete(true);
  20943. editor.selection.setContent(String.fromCharCode(e.charCode));
  20944. }
  20945. });
  20946. editor.addCommand('Delete', function() {
  20947. customDelete();
  20948. });
  20949. editor.addCommand('ForwardDelete', function() {
  20950. customDelete(true);
  20951. });
  20952. // Older WebKits doesn't properly handle the clipboard so we can't add the rest
  20953. if (olderWebKit) {
  20954. return;
  20955. }
  20956. editor.on('dragstart', function(e) {
  20957. var selectionHtml;
  20958. if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
  20959. selection.select(e.target);
  20960. }
  20961. dragStartRng = selection.getRng();
  20962. selectionHtml = editor.selection.getContent();
  20963. // Safari doesn't support custom dataTransfer items so we can only use URL and Text
  20964. if (selectionHtml.length > 0) {
  20965. e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(selectionHtml));
  20966. }
  20967. });
  20968. editor.on('drop', function(e) {
  20969. if (!isDefaultPrevented(e)) {
  20970. var internalContent = e.dataTransfer.getData('URL');
  20971. if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) {
  20972. return;
  20973. }
  20974. internalContent = unescape(internalContent.substr(urlPrefix.length));
  20975. if (doc.caretRangeFromPoint) {
  20976. e.preventDefault();
  20977. // Safari has a weird issue where drag/dropping images sometimes
  20978. // produces a green plus icon. When this happens the caretRangeFromPoint
  20979. // will return "null" even though the x, y coordinate is correct.
  20980. // But if we detach the insert from the drop event we will get a proper range
  20981. window.setTimeout(function() {
  20982. var pointRng = doc.caretRangeFromPoint(e.x, e.y);
  20983. if (dragStartRng) {
  20984. selection.setRng(dragStartRng);
  20985. dragStartRng = null;
  20986. }
  20987. customDelete();
  20988. selection.setRng(pointRng);
  20989. editor.insertContent(internalContent);
  20990. }, 0);
  20991. }
  20992. }
  20993. });
  20994. editor.on('cut', function(e) {
  20995. if (!isDefaultPrevented(e) && e.clipboardData) {
  20996. e.preventDefault();
  20997. e.clipboardData.clearData();
  20998. e.clipboardData.setData('text/html', editor.selection.getContent());
  20999. e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
  21000. customDelete(true);
  21001. }
  21002. });
  21003. }
  21004. /**
  21005. * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
  21006. *
  21007. * For example:
  21008. * <p><b>|</b></p>
  21009. *
  21010. * Or:
  21011. * <h1>|</h1>
  21012. *
  21013. * Or:
  21014. * [<h1></h1>]
  21015. */
  21016. function emptyEditorWhenDeleting() {
  21017. function serializeRng(rng) {
  21018. var body = dom.create("body");
  21019. var contents = rng.cloneContents();
  21020. body.appendChild(contents);
  21021. return selection.serializer.serialize(body, {format: 'html'});
  21022. }
  21023. function allContentsSelected(rng) {
  21024. if (!rng.setStart) {
  21025. if (rng.item) {
  21026. return false;
  21027. }
  21028. var bodyRng = rng.duplicate();
  21029. bodyRng.moveToElementText(editor.getBody());
  21030. return RangeUtils.compareRanges(rng, bodyRng);
  21031. }
  21032. var selection = serializeRng(rng);
  21033. var allRng = dom.createRng();
  21034. allRng.selectNode(editor.getBody());
  21035. var allSelection = serializeRng(allRng);
  21036. return selection === allSelection;
  21037. }
  21038. editor.on('keydown', function(e) {
  21039. var keyCode = e.keyCode, isCollapsed, body;
  21040. // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
  21041. if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
  21042. isCollapsed = editor.selection.isCollapsed();
  21043. body = editor.getBody();
  21044. // Selection is collapsed but the editor isn't empty
  21045. if (isCollapsed && !dom.isEmpty(body)) {
  21046. return;
  21047. }
  21048. // Selection isn't collapsed but not all the contents is selected
  21049. if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
  21050. return;
  21051. }
  21052. // Manually empty the editor
  21053. e.preventDefault();
  21054. editor.setContent('');
  21055. if (body.firstChild && dom.isBlock(body.firstChild)) {
  21056. editor.selection.setCursorLocation(body.firstChild, 0);
  21057. } else {
  21058. editor.selection.setCursorLocation(body, 0);
  21059. }
  21060. editor.nodeChanged();
  21061. }
  21062. });
  21063. }
  21064. /**
  21065. * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
  21066. * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
  21067. * This selects the whole body so that backspace/delete logic will delete everything
  21068. */
  21069. function selectAll() {
  21070. editor.on('keydown', function(e) {
  21071. if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
  21072. e.preventDefault();
  21073. editor.execCommand('SelectAll');
  21074. }
  21075. });
  21076. }
  21077. /**
  21078. * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
  21079. * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
  21080. *
  21081. * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
  21082. * you enter a character into the editor.
  21083. *
  21084. * It also happens when the first focus in made to the body.
  21085. *
  21086. * See: https://bugs.webkit.org/show_bug.cgi?id=83566
  21087. */
  21088. function inputMethodFocus() {
  21089. if (!editor.settings.content_editable) {
  21090. // Case 1 IME doesn't initialize if you focus the document
  21091. dom.bind(editor.getDoc(), 'focusin', function() {
  21092. selection.setRng(selection.getRng());
  21093. });
  21094. // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
  21095. dom.bind(editor.getDoc(), 'mousedown', function(e) {
  21096. if (e.target == editor.getDoc().documentElement) {
  21097. editor.getBody().focus();
  21098. selection.setRng(selection.getRng());
  21099. }
  21100. });
  21101. }
  21102. }
  21103. /**
  21104. * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
  21105. * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
  21106. * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
  21107. * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
  21108. * browsers.
  21109. *
  21110. * It also fixes a bug on Firefox where it's impossible to delete HR elements.
  21111. */
  21112. function removeHrOnBackspace() {
  21113. editor.on('keydown', function(e) {
  21114. if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  21115. if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  21116. var node = selection.getNode();
  21117. var previousSibling = node.previousSibling;
  21118. if (node.nodeName == 'HR') {
  21119. dom.remove(node);
  21120. e.preventDefault();
  21121. return;
  21122. }
  21123. if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
  21124. dom.remove(previousSibling);
  21125. e.preventDefault();
  21126. }
  21127. }
  21128. }
  21129. });
  21130. }
  21131. /**
  21132. * Firefox 3.x has an issue where the body element won't get proper focus if you click out
  21133. * side it's rectangle.
  21134. */
  21135. function focusBody() {
  21136. // Fix for a focus bug in FF 3.x where the body element
  21137. // wouldn't get proper focus if the user clicked on the HTML element
  21138. if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
  21139. editor.on('mousedown', function(e) {
  21140. if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
  21141. var body = editor.getBody();
  21142. // Blur the body it's focused but not correctly focused
  21143. body.blur();
  21144. // Refocus the body after a little while
  21145. setTimeout(function() {
  21146. body.focus();
  21147. }, 0);
  21148. }
  21149. });
  21150. }
  21151. }
  21152. /**
  21153. * WebKit has a bug where it isn't possible to select image, hr or anchor elements
  21154. * by clicking on them so we need to fake that.
  21155. */
  21156. function selectControlElements() {
  21157. editor.on('click', function(e) {
  21158. e = e.target;
  21159. // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
  21160. // WebKit can't even do simple things like selecting an image
  21161. // Needs tobe the setBaseAndExtend or it will fail to select floated images
  21162. if (/^(IMG|HR)$/.test(e.nodeName)) {
  21163. selection.getSel().setBaseAndExtent(e, 0, e, 1);
  21164. }
  21165. if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
  21166. selection.select(e);
  21167. }
  21168. editor.nodeChanged();
  21169. });
  21170. }
  21171. /**
  21172. * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
  21173. *
  21174. * Fixes do backspace/delete on this:
  21175. * <p>bla[ck</p><p style="color:red">r]ed</p>
  21176. *
  21177. * Would become:
  21178. * <p>bla|ed</p>
  21179. *
  21180. * Instead of:
  21181. * <p style="color:red">bla|ed</p>
  21182. */
  21183. function removeStylesWhenDeletingAcrossBlockElements() {
  21184. function getAttributeApplyFunction() {
  21185. var template = dom.getAttribs(selection.getStart().cloneNode(false));
  21186. return function() {
  21187. var target = selection.getStart();
  21188. if (target !== editor.getBody()) {
  21189. dom.setAttrib(target, "style", null);
  21190. each(template, function(attr) {
  21191. target.setAttributeNode(attr.cloneNode(true));
  21192. });
  21193. }
  21194. };
  21195. }
  21196. function isSelectionAcrossElements() {
  21197. return !selection.isCollapsed() &&
  21198. dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
  21199. }
  21200. editor.on('keypress', function(e) {
  21201. var applyAttributes;
  21202. if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
  21203. applyAttributes = getAttributeApplyFunction();
  21204. editor.getDoc().execCommand('delete', false, null);
  21205. applyAttributes();
  21206. e.preventDefault();
  21207. return false;
  21208. }
  21209. });
  21210. dom.bind(editor.getDoc(), 'cut', function(e) {
  21211. var applyAttributes;
  21212. if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
  21213. applyAttributes = getAttributeApplyFunction();
  21214. setTimeout(function() {
  21215. applyAttributes();
  21216. }, 0);
  21217. }
  21218. });
  21219. }
  21220. /**
  21221. * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange
  21222. * event every 50ms since it would other wise update the UI when you type and it hogs the CPU.
  21223. */
  21224. function selectionChangeNodeChanged() {
  21225. var lastRng, selectionTimer;
  21226. editor.on('selectionchange', function() {
  21227. if (selectionTimer) {
  21228. clearTimeout(selectionTimer);
  21229. selectionTimer = 0;
  21230. }
  21231. selectionTimer = window.setTimeout(function() {
  21232. if (editor.removed) {
  21233. return;
  21234. }
  21235. var rng = selection.getRng();
  21236. // Compare the ranges to see if it was a real change or not
  21237. if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) {
  21238. editor.nodeChanged();
  21239. lastRng = rng;
  21240. }
  21241. }, 50);
  21242. });
  21243. }
  21244. /**
  21245. * Screen readers on IE needs to have the role application set on the body.
  21246. */
  21247. function ensureBodyHasRoleApplication() {
  21248. document.body.setAttribute("role", "application");
  21249. }
  21250. /**
  21251. * Backspacing into a table behaves differently depending upon browser type.
  21252. * Therefore, disable Backspace when cursor immediately follows a table.
  21253. */
  21254. function disableBackspaceIntoATable() {
  21255. editor.on('keydown', function(e) {
  21256. if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  21257. if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  21258. var previousSibling = selection.getNode().previousSibling;
  21259. if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
  21260. e.preventDefault();
  21261. return false;
  21262. }
  21263. }
  21264. }
  21265. });
  21266. }
  21267. /**
  21268. * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
  21269. * logic adds a \n before the BR so that it will get rendered.
  21270. */
  21271. function addNewLinesBeforeBrInPre() {
  21272. // IE8+ rendering mode does the right thing with BR in PRE
  21273. if (getDocumentMode() > 7) {
  21274. return;
  21275. }
  21276. // Enable display: none in area and add a specific class that hides all BR elements in PRE to
  21277. // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
  21278. setEditorCommandState('RespectVisibilityInDesign', true);
  21279. editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
  21280. dom.addClass(editor.getBody(), 'mceHideBrInPre');
  21281. // Adds a \n before all BR elements in PRE to get them visual
  21282. parser.addNodeFilter('pre', function(nodes) {
  21283. var i = nodes.length, brNodes, j, brElm, sibling;
  21284. while (i--) {
  21285. brNodes = nodes[i].getAll('br');
  21286. j = brNodes.length;
  21287. while (j--) {
  21288. brElm = brNodes[j];
  21289. // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
  21290. sibling = brElm.prev;
  21291. if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
  21292. sibling.value += '\n';
  21293. } else {
  21294. brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
  21295. }
  21296. }
  21297. }
  21298. });
  21299. // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
  21300. serializer.addNodeFilter('pre', function(nodes) {
  21301. var i = nodes.length, brNodes, j, brElm, sibling;
  21302. while (i--) {
  21303. brNodes = nodes[i].getAll('br');
  21304. j = brNodes.length;
  21305. while (j--) {
  21306. brElm = brNodes[j];
  21307. sibling = brElm.prev;
  21308. if (sibling && sibling.type == 3) {
  21309. sibling.value = sibling.value.replace(/\r?\n$/, '');
  21310. }
  21311. }
  21312. }
  21313. });
  21314. }
  21315. /**
  21316. * Moves style width/height to attribute width/height when the user resizes an image on IE.
  21317. */
  21318. function removePreSerializedStylesWhenSelectingControls() {
  21319. dom.bind(editor.getBody(), 'mouseup', function() {
  21320. var value, node = selection.getNode();
  21321. // Moved styles to attributes on IMG eements
  21322. if (node.nodeName == 'IMG') {
  21323. // Convert style width to width attribute
  21324. if ((value = dom.getStyle(node, 'width'))) {
  21325. dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
  21326. dom.setStyle(node, 'width', '');
  21327. }
  21328. // Convert style height to height attribute
  21329. if ((value = dom.getStyle(node, 'height'))) {
  21330. dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
  21331. dom.setStyle(node, 'height', '');
  21332. }
  21333. }
  21334. });
  21335. }
  21336. /**
  21337. * Removes a blockquote when backspace is pressed at the beginning of it.
  21338. *
  21339. * For example:
  21340. * <blockquote><p>|x</p></blockquote>
  21341. *
  21342. * Becomes:
  21343. * <p>|x</p>
  21344. */
  21345. function removeBlockQuoteOnBackSpace() {
  21346. // Add block quote deletion handler
  21347. editor.on('keydown', function(e) {
  21348. var rng, container, offset, root, parent;
  21349. if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
  21350. return;
  21351. }
  21352. rng = selection.getRng();
  21353. container = rng.startContainer;
  21354. offset = rng.startOffset;
  21355. root = dom.getRoot();
  21356. parent = container;
  21357. if (!rng.collapsed || offset !== 0) {
  21358. return;
  21359. }
  21360. while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
  21361. parent = parent.parentNode;
  21362. }
  21363. // Is the cursor at the beginning of a blockquote?
  21364. if (parent.tagName === 'BLOCKQUOTE') {
  21365. // Remove the blockquote
  21366. editor.formatter.toggle('blockquote', null, parent);
  21367. // Move the caret to the beginning of container
  21368. rng = dom.createRng();
  21369. rng.setStart(container, 0);
  21370. rng.setEnd(container, 0);
  21371. selection.setRng(rng);
  21372. }
  21373. });
  21374. }
  21375. /**
  21376. * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
  21377. */
  21378. function setGeckoEditingOptions() {
  21379. function setOpts() {
  21380. editor._refreshContentEditable();
  21381. setEditorCommandState("StyleWithCSS", false);
  21382. setEditorCommandState("enableInlineTableEditing", false);
  21383. if (!settings.object_resizing) {
  21384. setEditorCommandState("enableObjectResizing", false);
  21385. }
  21386. }
  21387. if (!settings.readonly) {
  21388. editor.on('BeforeExecCommand MouseDown', setOpts);
  21389. }
  21390. }
  21391. /**
  21392. * Fixes a gecko link bug, when a link is placed at the end of block elements there is
  21393. * no way to move the caret behind the link. This fix adds a bogus br element after the link.
  21394. *
  21395. * For example this:
  21396. * <p><b><a href="#">x</a></b></p>
  21397. *
  21398. * Becomes this:
  21399. * <p><b><a href="#">x</a></b><br></p>
  21400. */
  21401. function addBrAfterLastLinks() {
  21402. function fixLinks() {
  21403. each(dom.select('a'), function(node) {
  21404. var parentNode = node.parentNode, root = dom.getRoot();
  21405. if (parentNode.lastChild === node) {
  21406. while (parentNode && !dom.isBlock(parentNode)) {
  21407. if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
  21408. return;
  21409. }
  21410. parentNode = parentNode.parentNode;
  21411. }
  21412. dom.add(parentNode, 'br', {'data-mce-bogus': 1});
  21413. }
  21414. });
  21415. }
  21416. editor.on('SetContent ExecCommand', function(e) {
  21417. if (e.type == "setcontent" || e.command === 'mceInsertLink') {
  21418. fixLinks();
  21419. }
  21420. });
  21421. }
  21422. /**
  21423. * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
  21424. * default we want to change that behavior.
  21425. */
  21426. function setDefaultBlockType() {
  21427. if (settings.forced_root_block) {
  21428. editor.on('init', function() {
  21429. setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
  21430. });
  21431. }
  21432. }
  21433. /**
  21434. * Removes ghost selections from images/tables on Gecko.
  21435. */
  21436. function removeGhostSelection() {
  21437. editor.on('Undo Redo SetContent', function(e) {
  21438. if (!e.initial) {
  21439. editor.execCommand('mceRepaint');
  21440. }
  21441. });
  21442. }
  21443. /**
  21444. * Deletes the selected image on IE instead of navigating to previous page.
  21445. */
  21446. function deleteControlItemOnBackSpace() {
  21447. editor.on('keydown', function(e) {
  21448. var rng;
  21449. if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
  21450. rng = editor.getDoc().selection.createRange();
  21451. if (rng && rng.item) {
  21452. e.preventDefault();
  21453. editor.undoManager.beforeChange();
  21454. dom.remove(rng.item(0));
  21455. editor.undoManager.add();
  21456. }
  21457. }
  21458. });
  21459. }
  21460. /**
  21461. * IE10 doesn't properly render block elements with the right height until you add contents to them.
  21462. * This fixes that by adding a padding-right to all empty text block elements.
  21463. * See: https://connect.microsoft.com/IE/feedback/details/743881
  21464. */
  21465. function renderEmptyBlocksFix() {
  21466. var emptyBlocksCSS;
  21467. // IE10+
  21468. if (getDocumentMode() >= 10) {
  21469. emptyBlocksCSS = '';
  21470. each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
  21471. emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
  21472. });
  21473. editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
  21474. }
  21475. }
  21476. /**
  21477. * Old IE versions can't retain contents within noscript elements so this logic will store the contents
  21478. * as a attribute and the insert that value as it's raw text when the DOM is serialized.
  21479. */
  21480. function keepNoScriptContents() {
  21481. if (getDocumentMode() < 9) {
  21482. parser.addNodeFilter('noscript', function(nodes) {
  21483. var i = nodes.length, node, textNode;
  21484. while (i--) {
  21485. node = nodes[i];
  21486. textNode = node.firstChild;
  21487. if (textNode) {
  21488. node.attr('data-mce-innertext', textNode.value);
  21489. }
  21490. }
  21491. });
  21492. serializer.addNodeFilter('noscript', function(nodes) {
  21493. var i = nodes.length, node, textNode, value;
  21494. while (i--) {
  21495. node = nodes[i];
  21496. textNode = nodes[i].firstChild;
  21497. if (textNode) {
  21498. textNode.value = Entities.decode(textNode.value);
  21499. } else {
  21500. // Old IE can't retain noscript value so an attribute is used to store it
  21501. value = node.attributes.map['data-mce-innertext'];
  21502. if (value) {
  21503. node.attr('data-mce-innertext', null);
  21504. textNode = new Node('#text', 3);
  21505. textNode.value = value;
  21506. textNode.raw = true;
  21507. node.append(textNode);
  21508. }
  21509. }
  21510. }
  21511. });
  21512. }
  21513. }
  21514. /**
  21515. * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
  21516. */
  21517. function fixCaretSelectionOfDocumentElementOnIe() {
  21518. var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
  21519. // Return range from point or null if it failed
  21520. function rngFromPoint(x, y) {
  21521. var rng = body.createTextRange();
  21522. try {
  21523. rng.moveToPoint(x, y);
  21524. } catch (ex) {
  21525. // IE sometimes throws and exception, so lets just ignore it
  21526. rng = null;
  21527. }
  21528. return rng;
  21529. }
  21530. // Fires while the selection is changing
  21531. function selectionChange(e) {
  21532. var pointRng;
  21533. // Check if the button is down or not
  21534. if (e.button) {
  21535. // Create range from mouse position
  21536. pointRng = rngFromPoint(e.x, e.y);
  21537. if (pointRng) {
  21538. // Check if pointRange is before/after selection then change the endPoint
  21539. if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
  21540. pointRng.setEndPoint('StartToStart', startRng);
  21541. } else {
  21542. pointRng.setEndPoint('EndToEnd', startRng);
  21543. }
  21544. pointRng.select();
  21545. }
  21546. } else {
  21547. endSelection();
  21548. }
  21549. }
  21550. // Removes listeners
  21551. function endSelection() {
  21552. var rng = doc.selection.createRange();
  21553. // If the range is collapsed then use the last start range
  21554. if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
  21555. startRng.select();
  21556. }
  21557. dom.unbind(doc, 'mouseup', endSelection);
  21558. dom.unbind(doc, 'mousemove', selectionChange);
  21559. startRng = started = 0;
  21560. }
  21561. // Make HTML element unselectable since we are going to handle selection by hand
  21562. doc.documentElement.unselectable = true;
  21563. // Detect when user selects outside BODY
  21564. dom.bind(doc, 'mousedown contextmenu', function(e) {
  21565. if (e.target.nodeName === 'HTML') {
  21566. if (started) {
  21567. endSelection();
  21568. }
  21569. // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
  21570. htmlElm = doc.documentElement;
  21571. if (htmlElm.scrollHeight > htmlElm.clientHeight) {
  21572. return;
  21573. }
  21574. started = 1;
  21575. // Setup start position
  21576. startRng = rngFromPoint(e.x, e.y);
  21577. if (startRng) {
  21578. // Listen for selection change events
  21579. dom.bind(doc, 'mouseup', endSelection);
  21580. dom.bind(doc, 'mousemove', selectionChange);
  21581. dom.getRoot().focus();
  21582. startRng.select();
  21583. }
  21584. }
  21585. });
  21586. }
  21587. /**
  21588. * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
  21589. * this fix will lean the caret right into the closest inline element.
  21590. */
  21591. function normalizeSelection() {
  21592. // 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
  21593. editor.on('keyup focusin mouseup', function(e) {
  21594. if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
  21595. selection.normalize();
  21596. }
  21597. }, true);
  21598. }
  21599. /**
  21600. * Forces Gecko to render a broken image icon if it fails to load an image.
  21601. */
  21602. function showBrokenImageIcon() {
  21603. editor.contentStyles.push(
  21604. 'img:-moz-broken {' +
  21605. '-moz-force-broken-image-icon:1;' +
  21606. 'min-width:24px;' +
  21607. 'min-height:24px' +
  21608. '}'
  21609. );
  21610. }
  21611. /**
  21612. * iOS has a bug where it's impossible to type if the document has a touchstart event
  21613. * bound and the user touches the document while having the on screen keyboard visible.
  21614. *
  21615. * The touch event moves the focus to the parent document while having the caret inside the iframe
  21616. * this fix moves the focus back into the iframe document.
  21617. */
  21618. function restoreFocusOnKeyDown() {
  21619. if (!editor.inline) {
  21620. editor.on('keydown', function() {
  21621. if (document.activeElement == document.body) {
  21622. editor.getWin().focus();
  21623. }
  21624. });
  21625. }
  21626. }
  21627. /**
  21628. * IE 11 has an annoying issue where you can't move focus into the editor
  21629. * by clicking on the white area HTML element. We used to be able to to fix this with
  21630. * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
  21631. * object it's not possible anymore. So we need to hack in a ungly CSS to force the
  21632. * body to be at least 150px. If the user clicks the HTML element out side this 150px region
  21633. * we simply move the focus into the first paragraph. Not ideal since you loose the
  21634. * positioning of the caret but goot enough for most cases.
  21635. */
  21636. function bodyHeight() {
  21637. if (!editor.inline) {
  21638. editor.contentStyles.push('body {min-height: 150px}');
  21639. editor.on('click', function(e) {
  21640. if (e.target.nodeName == 'HTML') {
  21641. editor.getBody().focus();
  21642. editor.selection.normalize();
  21643. editor.nodeChanged();
  21644. }
  21645. });
  21646. }
  21647. }
  21648. /**
  21649. * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
  21650. * You might then loose all your work so we need to block that behavior and replace it with our own.
  21651. */
  21652. function blockCmdArrowNavigation() {
  21653. if (Env.mac) {
  21654. editor.on('keydown', function(e) {
  21655. if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) {
  21656. e.preventDefault();
  21657. editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
  21658. }
  21659. });
  21660. }
  21661. }
  21662. /**
  21663. * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
  21664. */
  21665. function disableAutoUrlDetect() {
  21666. setEditorCommandState("AutoUrlDetect", false);
  21667. }
  21668. /**
  21669. * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
  21670. * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
  21671. * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
  21672. * but not as the lastChild of the body. However is we add a BR element to the body then remove it
  21673. * it doesn't seem to add these BR elements makes sence right?!
  21674. *
  21675. * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
  21676. */
  21677. function doubleTrailingBrElements() {
  21678. if (!editor.inline) {
  21679. editor.on('focus blur beforegetcontent', function() {
  21680. var br = editor.dom.create('br');
  21681. editor.getBody().appendChild(br);
  21682. br.parentNode.removeChild(br);
  21683. }, true);
  21684. }
  21685. }
  21686. /**
  21687. * iOS 7.1 introduced two new bugs:
  21688. * 1) It's possible to open links within a contentEditable area by clicking on them.
  21689. * 2) If you hold down the finger it will display the link/image touch callout menu.
  21690. */
  21691. function tapLinksAndImages() {
  21692. editor.on('click', function(e) {
  21693. var elm = e.target;
  21694. do {
  21695. if (elm.tagName === 'A') {
  21696. e.preventDefault();
  21697. return;
  21698. }
  21699. } while ((elm = elm.parentNode));
  21700. });
  21701. editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
  21702. }
  21703. /**
  21704. * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
  21705. * For example this: <form><button></form>
  21706. */
  21707. function blockFormSubmitInsideEditor() {
  21708. editor.on('init', function() {
  21709. editor.dom.bind(editor.getBody(), 'submit', function(e) {
  21710. e.preventDefault();
  21711. });
  21712. });
  21713. }
  21714. // All browsers
  21715. disableBackspaceIntoATable();
  21716. removeBlockQuoteOnBackSpace();
  21717. emptyEditorWhenDeleting();
  21718. normalizeSelection();
  21719. // WebKit
  21720. if (isWebKit) {
  21721. cleanupStylesWhenDeleting();
  21722. inputMethodFocus();
  21723. selectControlElements();
  21724. setDefaultBlockType();
  21725. blockFormSubmitInsideEditor();
  21726. // iOS
  21727. if (Env.iOS) {
  21728. selectionChangeNodeChanged();
  21729. restoreFocusOnKeyDown();
  21730. bodyHeight();
  21731. tapLinksAndImages();
  21732. } else {
  21733. selectAll();
  21734. }
  21735. }
  21736. // IE
  21737. if (isIE && Env.ie < 11) {
  21738. removeHrOnBackspace();
  21739. ensureBodyHasRoleApplication();
  21740. addNewLinesBeforeBrInPre();
  21741. removePreSerializedStylesWhenSelectingControls();
  21742. deleteControlItemOnBackSpace();
  21743. renderEmptyBlocksFix();
  21744. keepNoScriptContents();
  21745. fixCaretSelectionOfDocumentElementOnIe();
  21746. }
  21747. if (Env.ie >= 11) {
  21748. bodyHeight();
  21749. doubleTrailingBrElements();
  21750. }
  21751. if (Env.ie) {
  21752. selectAll();
  21753. disableAutoUrlDetect();
  21754. }
  21755. // Gecko
  21756. if (isGecko) {
  21757. removeHrOnBackspace();
  21758. focusBody();
  21759. removeStylesWhenDeletingAcrossBlockElements();
  21760. setGeckoEditingOptions();
  21761. addBrAfterLastLinks();
  21762. removeGhostSelection();
  21763. showBrokenImageIcon();
  21764. blockCmdArrowNavigation();
  21765. }
  21766. };
  21767. });
  21768. // Included from: js/tinymce/classes/util/Observable.js
  21769. /**
  21770. * Observable.js
  21771. *
  21772. * Copyright, Moxiecode Systems AB
  21773. * Released under LGPL License.
  21774. *
  21775. * License: http://www.tinymce.com/license
  21776. * Contributing: http://www.tinymce.com/contributing
  21777. */
  21778. /**
  21779. * This mixin will add event binding logic to classes.
  21780. *
  21781. * @mixin tinymce.util.Observable
  21782. */
  21783. define("tinymce/util/Observable", [
  21784. "tinymce/util/Tools"
  21785. ], function(Tools) {
  21786. var bindingsName = "__bindings";
  21787. var nativeEvents = Tools.makeMap(
  21788. "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" +
  21789. " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragstart dragend dragover draggesture dragdrop drop drag", ' '
  21790. );
  21791. function returnFalse() {
  21792. return false;
  21793. }
  21794. function returnTrue() {
  21795. return true;
  21796. }
  21797. return {
  21798. /**
  21799. * Fires the specified event by name.
  21800. *
  21801. * @method fire
  21802. * @param {String} name Name of the event to fire.
  21803. * @param {tinymce.Event/Object?} args Event arguments.
  21804. * @param {Boolean?} bubble True/false if the event is to be bubbled.
  21805. * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance.
  21806. * @example
  21807. * instance.fire('event', {...});
  21808. */
  21809. fire: function(name, args, bubble) {
  21810. var self = this, handlers, i, l, callback, parent;
  21811. name = name.toLowerCase();
  21812. args = args || {};
  21813. args.type = name;
  21814. // Prevent all events except the remove event after the editor has been removed
  21815. if (self.removed && name !== "remove") {
  21816. return;
  21817. }
  21818. // Setup target is there isn't one
  21819. if (!args.target) {
  21820. args.target = self;
  21821. }
  21822. // Add event delegation methods if they are missing
  21823. if (!args.preventDefault) {
  21824. // Add preventDefault method
  21825. args.preventDefault = function() {
  21826. args.isDefaultPrevented = returnTrue;
  21827. };
  21828. // Add stopPropagation
  21829. args.stopPropagation = function() {
  21830. args.isPropagationStopped = returnTrue;
  21831. };
  21832. // Add stopImmediatePropagation
  21833. args.stopImmediatePropagation = function() {
  21834. args.isImmediatePropagationStopped = returnTrue;
  21835. };
  21836. // Add event delegation states
  21837. args.isDefaultPrevented = returnFalse;
  21838. args.isPropagationStopped = returnFalse;
  21839. args.isImmediatePropagationStopped = returnFalse;
  21840. }
  21841. //console.log(name, args);
  21842. if (self[bindingsName]) {
  21843. handlers = self[bindingsName][name];
  21844. if (handlers) {
  21845. for (i = 0, l = handlers.length; i < l; i++) {
  21846. handlers[i] = callback = handlers[i];
  21847. // Stop immediate propagation if needed
  21848. if (args.isImmediatePropagationStopped()) {
  21849. break;
  21850. }
  21851. // If callback returns false then prevent default and stop all propagation
  21852. if (callback.call(self, args) === false) {
  21853. args.preventDefault();
  21854. return args;
  21855. }
  21856. }
  21857. }
  21858. }
  21859. // Bubble event up to parents
  21860. if (bubble !== false && self.parent) {
  21861. parent = self.parent();
  21862. while (parent && !args.isPropagationStopped()) {
  21863. parent.fire(name, args, false);
  21864. parent = parent.parent();
  21865. }
  21866. }
  21867. return args;
  21868. },
  21869. /**
  21870. * Binds an event listener to a specific event by name.
  21871. *
  21872. * @method on
  21873. * @param {String} name Event name or space separated list of events to bind.
  21874. * @param {callback} callback Callback to be executed when the event occurs.
  21875. * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  21876. * @return {Object} Current class instance.
  21877. * @example
  21878. * instance.on('event', function(e) {
  21879. * // Callback logic
  21880. * });
  21881. */
  21882. on: function(name, callback, prepend) {
  21883. var self = this, bindings, handlers, names, i;
  21884. if (callback === false) {
  21885. callback = function() {
  21886. return false;
  21887. };
  21888. }
  21889. if (callback) {
  21890. names = name.toLowerCase().split(' ');
  21891. i = names.length;
  21892. while (i--) {
  21893. name = names[i];
  21894. bindings = self[bindingsName];
  21895. if (!bindings) {
  21896. bindings = self[bindingsName] = {};
  21897. }
  21898. handlers = bindings[name];
  21899. if (!handlers) {
  21900. handlers = bindings[name] = [];
  21901. if (self.bindNative && nativeEvents[name]) {
  21902. self.bindNative(name);
  21903. }
  21904. }
  21905. if (prepend) {
  21906. handlers.unshift(callback);
  21907. } else {
  21908. handlers.push(callback);
  21909. }
  21910. }
  21911. }
  21912. return self;
  21913. },
  21914. /**
  21915. * Unbinds an event listener to a specific event by name.
  21916. *
  21917. * @method off
  21918. * @param {String?} name Name of the event to unbind.
  21919. * @param {callback?} callback Callback to unbind.
  21920. * @return {Object} Current class instance.
  21921. * @example
  21922. * // Unbind specific callback
  21923. * instance.off('event', handler);
  21924. *
  21925. * // Unbind all listeners by name
  21926. * instance.off('event');
  21927. *
  21928. * // Unbind all events
  21929. * instance.off();
  21930. */
  21931. off: function(name, callback) {
  21932. var self = this, i, bindings = self[bindingsName], handlers, bindingName, names, hi;
  21933. if (bindings) {
  21934. if (name) {
  21935. names = name.toLowerCase().split(' ');
  21936. i = names.length;
  21937. while (i--) {
  21938. name = names[i];
  21939. handlers = bindings[name];
  21940. // Unbind all handlers
  21941. if (!name) {
  21942. for (bindingName in bindings) {
  21943. bindings[name].length = 0;
  21944. }
  21945. return self;
  21946. }
  21947. if (handlers) {
  21948. // Unbind all by name
  21949. if (!callback) {
  21950. handlers.length = 0;
  21951. } else {
  21952. // Unbind specific ones
  21953. hi = handlers.length;
  21954. while (hi--) {
  21955. if (handlers[hi] === callback) {
  21956. handlers.splice(hi, 1);
  21957. }
  21958. }
  21959. }
  21960. if (!handlers.length && self.unbindNative && nativeEvents[name]) {
  21961. self.unbindNative(name);
  21962. delete bindings[name];
  21963. }
  21964. }
  21965. }
  21966. } else {
  21967. if (self.unbindNative) {
  21968. for (name in bindings) {
  21969. self.unbindNative(name);
  21970. }
  21971. }
  21972. self[bindingsName] = [];
  21973. }
  21974. }
  21975. return self;
  21976. },
  21977. hasEventListeners: function(name) {
  21978. var bindings = this[bindingsName];
  21979. name = name.toLowerCase();
  21980. return !(!bindings || !bindings[name] || bindings[name].length === 0);
  21981. }
  21982. };
  21983. });
  21984. // Included from: js/tinymce/classes/Shortcuts.js
  21985. /**
  21986. * Shortcuts.js
  21987. *
  21988. * Copyright, Moxiecode Systems AB
  21989. * Released under LGPL License.
  21990. *
  21991. * License: http://www.tinymce.com/license
  21992. * Contributing: http://www.tinymce.com/contributing
  21993. */
  21994. /**
  21995. * Contains all logic for handling of keyboard shortcuts.
  21996. */
  21997. define("tinymce/Shortcuts", [
  21998. "tinymce/util/Tools",
  21999. "tinymce/Env"
  22000. ], function(Tools, Env) {
  22001. var each = Tools.each, explode = Tools.explode;
  22002. var keyCodeLookup = {
  22003. "f9": 120,
  22004. "f10": 121,
  22005. "f11": 122
  22006. };
  22007. return function(editor) {
  22008. var self = this, shortcuts = {};
  22009. editor.on('keyup keypress keydown', function(e) {
  22010. if (e.altKey || e.ctrlKey || e.metaKey) {
  22011. each(shortcuts, function(shortcut) {
  22012. var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey;
  22013. if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
  22014. return;
  22015. }
  22016. if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
  22017. e.preventDefault();
  22018. if (e.type == "keydown") {
  22019. shortcut.func.call(shortcut.scope);
  22020. }
  22021. return true;
  22022. }
  22023. });
  22024. }
  22025. });
  22026. /**
  22027. * Adds a keyboard shortcut for some command or function.
  22028. *
  22029. * @method addShortcut
  22030. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  22031. * @param {String} desc Text description for the command.
  22032. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  22033. * @param {Object} sc Optional scope to execute the function in.
  22034. * @return {Boolean} true/false state if the shortcut was added or not.
  22035. */
  22036. self.add = function(pattern, desc, cmdFunc, scope) {
  22037. var cmd;
  22038. cmd = cmdFunc;
  22039. if (typeof(cmdFunc) === 'string') {
  22040. cmdFunc = function() {
  22041. editor.execCommand(cmd, false, null);
  22042. };
  22043. } else if (Tools.isArray(cmd)) {
  22044. cmdFunc = function() {
  22045. editor.execCommand(cmd[0], cmd[1], cmd[2]);
  22046. };
  22047. }
  22048. each(explode(pattern.toLowerCase()), function(pattern) {
  22049. var shortcut = {
  22050. func: cmdFunc,
  22051. scope: scope || editor,
  22052. desc: editor.translate(desc),
  22053. alt: false,
  22054. ctrl: false,
  22055. shift: false
  22056. };
  22057. each(explode(pattern, '+'), function(value) {
  22058. switch (value) {
  22059. case 'alt':
  22060. case 'ctrl':
  22061. case 'shift':
  22062. shortcut[value] = true;
  22063. break;
  22064. default:
  22065. shortcut.charCode = value.charCodeAt(0);
  22066. shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
  22067. }
  22068. });
  22069. shortcuts[
  22070. (shortcut.ctrl ? 'ctrl' : '') + ',' +
  22071. (shortcut.alt ? 'alt' : '') + ',' +
  22072. (shortcut.shift ? 'shift' : '') + ',' +
  22073. shortcut.keyCode
  22074. ] = shortcut;
  22075. });
  22076. return true;
  22077. };
  22078. };
  22079. });
  22080. // Included from: js/tinymce/classes/Editor.js
  22081. /**
  22082. * Editor.js
  22083. *
  22084. * Copyright, Moxiecode Systems AB
  22085. * Released under LGPL License.
  22086. *
  22087. * License: http://www.tinymce.com/license
  22088. * Contributing: http://www.tinymce.com/contributing
  22089. */
  22090. /*jshint scripturl:true */
  22091. /**
  22092. * Include the base event class documentation.
  22093. *
  22094. * @include ../../../tools/docs/tinymce.Event.js
  22095. */
  22096. /**
  22097. * This class contains the core logic for a TinyMCE editor.
  22098. *
  22099. * @class tinymce.Editor
  22100. * @mixes tinymce.util.Observable
  22101. * @example
  22102. * // Add a class to all paragraphs in the editor.
  22103. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  22104. *
  22105. * // Gets the current editors selection as text
  22106. * tinymce.activeEditor.selection.getContent({format: 'text'});
  22107. *
  22108. * // Creates a new editor instance
  22109. * var ed = new tinymce.Editor('textareaid', {
  22110. * some_setting: 1
  22111. * }, tinymce.EditorManager);
  22112. *
  22113. * // Select each item the user clicks on
  22114. * ed.on('click', function(e) {
  22115. * ed.selection.select(e.target);
  22116. * });
  22117. *
  22118. * ed.render();
  22119. */
  22120. define("tinymce/Editor", [
  22121. "tinymce/dom/DOMUtils",
  22122. "tinymce/AddOnManager",
  22123. "tinymce/html/Node",
  22124. "tinymce/dom/Serializer",
  22125. "tinymce/html/Serializer",
  22126. "tinymce/dom/Selection",
  22127. "tinymce/Formatter",
  22128. "tinymce/UndoManager",
  22129. "tinymce/EnterKey",
  22130. "tinymce/ForceBlocks",
  22131. "tinymce/EditorCommands",
  22132. "tinymce/util/URI",
  22133. "tinymce/dom/ScriptLoader",
  22134. "tinymce/dom/EventUtils",
  22135. "tinymce/WindowManager",
  22136. "tinymce/html/Schema",
  22137. "tinymce/html/DomParser",
  22138. "tinymce/util/Quirks",
  22139. "tinymce/Env",
  22140. "tinymce/util/Tools",
  22141. "tinymce/util/Observable",
  22142. "tinymce/Shortcuts"
  22143. ], function(
  22144. DOMUtils, AddOnManager, Node, DomSerializer, Serializer,
  22145. Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
  22146. URI, ScriptLoader, EventUtils, WindowManager,
  22147. Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts
  22148. ) {
  22149. // Shorten these names
  22150. var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
  22151. var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
  22152. var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
  22153. var Event = EventUtils.Event;
  22154. var isGecko = Env.gecko, ie = Env.ie;
  22155. function getEventTarget(editor, eventName) {
  22156. if (eventName == 'selectionchange') {
  22157. return editor.getDoc();
  22158. }
  22159. // Need to bind mousedown/mouseup etc to document not body in iframe mode
  22160. // Since the user might click on the HTML element not the BODY
  22161. if (!editor.inline && /^mouse|click|contextmenu|drop/.test(eventName)) {
  22162. return editor.getDoc();
  22163. }
  22164. return editor.getBody();
  22165. }
  22166. /**
  22167. * Include documentation for all the events.
  22168. *
  22169. * @include ../../../tools/docs/tinymce.Editor.js
  22170. */
  22171. /**
  22172. * Constructs a editor instance by id.
  22173. *
  22174. * @constructor
  22175. * @method Editor
  22176. * @param {String} id Unique id for the editor.
  22177. * @param {Object} settings Settings for the editor.
  22178. * @param {tinymce.EditorManager} editorManager EditorManager instance.
  22179. * @author Moxiecode
  22180. */
  22181. function Editor(id, settings, editorManager) {
  22182. var self = this, documentBaseUrl, baseUri;
  22183. documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
  22184. baseUri = editorManager.baseURI;
  22185. /**
  22186. * Name/value collection with editor settings.
  22187. *
  22188. * @property settings
  22189. * @type Object
  22190. * @example
  22191. * // Get the value of the theme setting
  22192. * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
  22193. */
  22194. self.settings = settings = extend({
  22195. id: id,
  22196. theme: 'modern',
  22197. delta_width: 0,
  22198. delta_height: 0,
  22199. popup_css: '',
  22200. plugins: '',
  22201. document_base_url: documentBaseUrl,
  22202. add_form_submit_trigger: true,
  22203. submit_patch: true,
  22204. add_unload_trigger: true,
  22205. convert_urls: true,
  22206. relative_urls: true,
  22207. remove_script_host: true,
  22208. object_resizing: true,
  22209. doctype: '<!DOCTYPE html>',
  22210. visual: true,
  22211. font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
  22212. // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
  22213. font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
  22214. forced_root_block: 'p',
  22215. hidden_input: true,
  22216. padd_empty_editor: true,
  22217. render_ui: true,
  22218. indentation: '30px',
  22219. inline_styles: true,
  22220. convert_fonts_to_spans: true,
  22221. indent: 'simple',
  22222. indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
  22223. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  22224. indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
  22225. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  22226. validate: true,
  22227. entity_encoding: 'named',
  22228. url_converter: self.convertURL,
  22229. url_converter_scope: self,
  22230. ie7_compat: true
  22231. }, settings);
  22232. AddOnManager.language = settings.language || 'en';
  22233. AddOnManager.languageLoad = settings.language_load;
  22234. AddOnManager.baseURL = editorManager.baseURL;
  22235. /**
  22236. * Editor instance id, normally the same as the div/textarea that was replaced.
  22237. *
  22238. * @property id
  22239. * @type String
  22240. */
  22241. self.id = settings.id = id;
  22242. /**
  22243. * State to force the editor to return false on a isDirty call.
  22244. *
  22245. * @property isNotDirty
  22246. * @type Boolean
  22247. * @example
  22248. * function ajaxSave() {
  22249. * var ed = tinymce.get('elm1');
  22250. *
  22251. * // Save contents using some XHR call
  22252. * alert(ed.getContent());
  22253. *
  22254. * ed.isNotDirty = true; // Force not dirty state
  22255. * }
  22256. */
  22257. self.isNotDirty = true;
  22258. /**
  22259. * Name/Value object containting plugin instances.
  22260. *
  22261. * @property plugins
  22262. * @type Object
  22263. * @example
  22264. * // Execute a method inside a plugin directly
  22265. * tinymce.activeEditor.plugins.someplugin.someMethod();
  22266. */
  22267. self.plugins = {};
  22268. /**
  22269. * URI object to document configured for the TinyMCE instance.
  22270. *
  22271. * @property documentBaseURI
  22272. * @type tinymce.util.URI
  22273. * @example
  22274. * // Get relative URL from the location of document_base_url
  22275. * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
  22276. *
  22277. * // Get absolute URL from the location of document_base_url
  22278. * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
  22279. */
  22280. self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
  22281. base_uri: baseUri
  22282. });
  22283. /**
  22284. * URI object to current document that holds the TinyMCE editor instance.
  22285. *
  22286. * @property baseURI
  22287. * @type tinymce.util.URI
  22288. * @example
  22289. * // Get relative URL from the location of the API
  22290. * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
  22291. *
  22292. * // Get absolute URL from the location of the API
  22293. * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
  22294. */
  22295. self.baseURI = baseUri;
  22296. /**
  22297. * Array with CSS files to load into the iframe.
  22298. *
  22299. * @property contentCSS
  22300. * @type Array
  22301. */
  22302. self.contentCSS = [];
  22303. /**
  22304. * Array of CSS styles to add to head of document when the editor loads.
  22305. *
  22306. * @property contentStyles
  22307. * @type Array
  22308. */
  22309. self.contentStyles = [];
  22310. // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
  22311. self.shortcuts = new Shortcuts(self);
  22312. // Internal command handler objects
  22313. self.execCommands = {};
  22314. self.queryStateCommands = {};
  22315. self.queryValueCommands = {};
  22316. self.loadedCSS = {};
  22317. self.suffix = editorManager.suffix;
  22318. self.editorManager = editorManager;
  22319. self.inline = settings.inline;
  22320. // Call setup
  22321. editorManager.fire('SetupEditor', self);
  22322. self.execCallback('setup', self);
  22323. }
  22324. Editor.prototype = {
  22325. /**
  22326. * Renderes the editor/adds it to the page.
  22327. *
  22328. * @method render
  22329. */
  22330. render: function() {
  22331. var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
  22332. function readyHandler() {
  22333. DOM.unbind(window, 'ready', readyHandler);
  22334. self.render();
  22335. }
  22336. // Page is not loaded yet, wait for it
  22337. if (!Event.domLoaded) {
  22338. DOM.bind(window, 'ready', readyHandler);
  22339. return;
  22340. }
  22341. // Element not found, then skip initialization
  22342. if (!self.getElement()) {
  22343. return;
  22344. }
  22345. // No editable support old iOS versions etc
  22346. if (!Env.contentEditable) {
  22347. return;
  22348. }
  22349. // Hide target element early to prevent content flashing
  22350. if (!settings.inline) {
  22351. self.orgVisibility = self.getElement().style.visibility;
  22352. self.getElement().style.visibility = 'hidden';
  22353. } else {
  22354. self.inline = true;
  22355. }
  22356. var form = self.getElement().form || DOM.getParent(id, 'form');
  22357. if (form) {
  22358. self.formElement = form;
  22359. // Add hidden input for non input elements inside form elements
  22360. if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
  22361. DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
  22362. self.hasHiddenInput = true;
  22363. }
  22364. // Pass submit/reset from form to editor instance
  22365. self.formEventDelegate = function(e) {
  22366. self.fire(e.type, e);
  22367. };
  22368. DOM.bind(form, 'submit reset', self.formEventDelegate);
  22369. // Reset contents in editor when the form is reset
  22370. self.on('reset', function() {
  22371. self.setContent(self.startContent, {format: 'raw'});
  22372. });
  22373. // Check page uses id="submit" or name="submit" for it's submit button
  22374. if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
  22375. form._mceOldSubmit = form.submit;
  22376. form.submit = function() {
  22377. self.editorManager.triggerSave();
  22378. self.isNotDirty = true;
  22379. return form._mceOldSubmit(form);
  22380. };
  22381. }
  22382. }
  22383. /**
  22384. * Window manager reference, use this to open new windows and dialogs.
  22385. *
  22386. * @property windowManager
  22387. * @type tinymce.WindowManager
  22388. * @example
  22389. * // Shows an alert message
  22390. * tinymce.activeEditor.windowManager.alert('Hello world!');
  22391. *
  22392. * // Opens a new dialog with the file.htm file and the size 320x240
  22393. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  22394. * tinymce.activeEditor.windowManager.open({
  22395. * url: 'file.htm',
  22396. * width: 320,
  22397. * height: 240
  22398. * }, {
  22399. * custom_param: 1
  22400. * });
  22401. */
  22402. self.windowManager = new WindowManager(self);
  22403. if (settings.encoding == 'xml') {
  22404. self.on('GetContent', function(e) {
  22405. if (e.save) {
  22406. e.content = DOM.encode(e.content);
  22407. }
  22408. });
  22409. }
  22410. if (settings.add_form_submit_trigger) {
  22411. self.on('submit', function() {
  22412. if (self.initialized) {
  22413. self.save();
  22414. }
  22415. });
  22416. }
  22417. if (settings.add_unload_trigger) {
  22418. self._beforeUnload = function() {
  22419. if (self.initialized && !self.destroyed && !self.isHidden()) {
  22420. self.save({format: 'raw', no_events: true, set_dirty: false});
  22421. }
  22422. };
  22423. self.editorManager.on('BeforeUnload', self._beforeUnload);
  22424. }
  22425. // Load scripts
  22426. function loadScripts() {
  22427. var scriptLoader = ScriptLoader.ScriptLoader;
  22428. if (settings.language && settings.language != 'en' && !settings.language_url) {
  22429. settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
  22430. }
  22431. if (settings.language_url) {
  22432. scriptLoader.add(settings.language_url);
  22433. }
  22434. if (settings.theme && typeof settings.theme != "function" &&
  22435. settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
  22436. var themeUrl = settings.theme_url;
  22437. if (themeUrl) {
  22438. themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
  22439. } else {
  22440. themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
  22441. }
  22442. ThemeManager.load(settings.theme, themeUrl);
  22443. }
  22444. if (Tools.isArray(settings.plugins)) {
  22445. settings.plugins = settings.plugins.join(' ');
  22446. }
  22447. each(settings.external_plugins, function(url, name) {
  22448. PluginManager.load(name, url);
  22449. settings.plugins += ' ' + name;
  22450. });
  22451. each(settings.plugins.split(/[ ,]/), function(plugin) {
  22452. plugin = trim(plugin);
  22453. if (plugin && !PluginManager.urls[plugin]) {
  22454. if (plugin.charAt(0) == '-') {
  22455. plugin = plugin.substr(1, plugin.length);
  22456. var dependencies = PluginManager.dependencies(plugin);
  22457. each(dependencies, function(dep) {
  22458. var defaultSettings = {
  22459. prefix:'plugins/',
  22460. resource: dep,
  22461. suffix:'/plugin' + suffix + '.js'
  22462. };
  22463. dep = PluginManager.createUrl(defaultSettings, dep);
  22464. PluginManager.load(dep.resource, dep);
  22465. });
  22466. } else {
  22467. PluginManager.load(plugin, {
  22468. prefix: 'plugins/',
  22469. resource: plugin,
  22470. suffix: '/plugin' + suffix + '.js'
  22471. });
  22472. }
  22473. }
  22474. });
  22475. scriptLoader.loadQueue(function() {
  22476. if (!self.removed) {
  22477. self.init();
  22478. }
  22479. });
  22480. }
  22481. loadScripts();
  22482. },
  22483. /**
  22484. * Initializes the editor this will be called automatically when
  22485. * all plugins/themes and language packs are loaded by the rendered method.
  22486. * This method will setup the iframe and create the theme and plugin instances.
  22487. *
  22488. * @method init
  22489. */
  22490. init: function() {
  22491. var self = this, settings = self.settings, elm = self.getElement();
  22492. var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
  22493. self.rtl = this.editorManager.i18n.rtl;
  22494. self.editorManager.add(self);
  22495. settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
  22496. /**
  22497. * Reference to the theme instance that was used to generate the UI.
  22498. *
  22499. * @property theme
  22500. * @type tinymce.Theme
  22501. * @example
  22502. * // Executes a method on the theme directly
  22503. * tinymce.activeEditor.theme.someMethod();
  22504. */
  22505. if (settings.theme) {
  22506. if (typeof settings.theme != "function") {
  22507. settings.theme = settings.theme.replace(/-/, '');
  22508. Theme = ThemeManager.get(settings.theme);
  22509. self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
  22510. if (self.theme.init) {
  22511. self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''));
  22512. }
  22513. } else {
  22514. self.theme = settings.theme;
  22515. }
  22516. }
  22517. function initPlugin(plugin) {
  22518. var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
  22519. pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
  22520. plugin = trim(plugin);
  22521. if (Plugin && inArray(initializedPlugins, plugin) === -1) {
  22522. each(PluginManager.dependencies(plugin), function(dep){
  22523. initPlugin(dep);
  22524. });
  22525. pluginInstance = new Plugin(self, pluginUrl);
  22526. self.plugins[plugin] = pluginInstance;
  22527. if (pluginInstance.init) {
  22528. pluginInstance.init(self, pluginUrl);
  22529. initializedPlugins.push(plugin);
  22530. }
  22531. }
  22532. }
  22533. // Create all plugins
  22534. each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
  22535. // Measure box
  22536. if (settings.render_ui && self.theme) {
  22537. self.orgDisplay = elm.style.display;
  22538. if (typeof settings.theme != "function") {
  22539. w = settings.width || elm.style.width || elm.offsetWidth;
  22540. h = settings.height || elm.style.height || elm.offsetHeight;
  22541. minHeight = settings.min_height || 100;
  22542. re = /^[0-9\.]+(|px)$/i;
  22543. if (re.test('' + w)) {
  22544. w = Math.max(parseInt(w, 10), 100);
  22545. }
  22546. if (re.test('' + h)) {
  22547. h = Math.max(parseInt(h, 10), minHeight);
  22548. }
  22549. // Render UI
  22550. o = self.theme.renderUI({
  22551. targetNode: elm,
  22552. width: w,
  22553. height: h,
  22554. deltaWidth: settings.delta_width,
  22555. deltaHeight: settings.delta_height
  22556. });
  22557. // Resize editor
  22558. if (!settings.content_editable) {
  22559. DOM.setStyles(o.sizeContainer || o.editorContainer, {
  22560. wi2dth: w,
  22561. // TODO: Fix this
  22562. h2eight: h
  22563. });
  22564. h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
  22565. if (h < minHeight) {
  22566. h = minHeight;
  22567. }
  22568. }
  22569. } else {
  22570. o = settings.theme(self, elm);
  22571. // Convert element type to id:s
  22572. if (o.editorContainer.nodeType) {
  22573. o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
  22574. }
  22575. // Convert element type to id:s
  22576. if (o.iframeContainer.nodeType) {
  22577. o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
  22578. }
  22579. // Use specified iframe height or the targets offsetHeight
  22580. h = o.iframeHeight || elm.offsetHeight;
  22581. }
  22582. self.editorContainer = o.editorContainer;
  22583. }
  22584. // Load specified content CSS last
  22585. if (settings.content_css) {
  22586. each(explode(settings.content_css), function(u) {
  22587. self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
  22588. });
  22589. }
  22590. // Load specified content CSS last
  22591. if (settings.content_style) {
  22592. self.contentStyles.push(settings.content_style);
  22593. }
  22594. // Content editable mode ends here
  22595. if (settings.content_editable) {
  22596. elm = n = o = null; // Fix IE leak
  22597. return self.initContentBody();
  22598. }
  22599. self.iframeHTML = settings.doctype + '<html><head>';
  22600. // We only need to override paths if we have to
  22601. // IE has a bug where it remove site absolute urls to relative ones if this is specified
  22602. if (settings.document_base_url != self.documentBaseUrl) {
  22603. self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
  22604. }
  22605. // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
  22606. if (!Env.caretAfter && settings.ie7_compat) {
  22607. self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
  22608. }
  22609. self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  22610. // Load the CSS by injecting them into the HTML this will reduce "flicker"
  22611. for (i = 0; i < self.contentCSS.length; i++) {
  22612. var cssUrl = self.contentCSS[i];
  22613. self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
  22614. self.loadedCSS[cssUrl] = true;
  22615. }
  22616. bodyId = settings.body_id || 'tinymce';
  22617. if (bodyId.indexOf('=') != -1) {
  22618. bodyId = self.getParam('body_id', '', 'hash');
  22619. bodyId = bodyId[self.id] || bodyId;
  22620. }
  22621. bodyClass = settings.body_class || '';
  22622. if (bodyClass.indexOf('=') != -1) {
  22623. bodyClass = self.getParam('body_class', '', 'hash');
  22624. bodyClass = bodyClass[self.id] || '';
  22625. }
  22626. self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' +
  22627. 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>';
  22628. /*eslint no-script-url:0 */
  22629. var domainRelaxUrl = 'javascript:(function(){' +
  22630. 'document.open();document.domain="' + document.domain + '";' +
  22631. 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
  22632. 'document.close();ed.initContentBody(true);})()';
  22633. // Domain relaxing is required since the user has messed around with document.domain
  22634. if (document.domain != location.hostname) {
  22635. url = domainRelaxUrl;
  22636. }
  22637. // Create iframe
  22638. // TODO: ACC add the appropriate description on this.
  22639. n = DOM.add(o.iframeContainer, 'iframe', {
  22640. id: self.id + "_ifr",
  22641. src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
  22642. frameBorder: '0',
  22643. allowTransparency: "true",
  22644. title: self.editorManager.translate(
  22645. "Rich Text Area. Press ALT-F9 for menu. " +
  22646. "Press ALT-F10 for toolbar. Press ALT-0 for help"
  22647. ),
  22648. style: {
  22649. width: '100%',
  22650. height: h,
  22651. display: 'block' // Important for Gecko to render the iframe correctly
  22652. }
  22653. });
  22654. // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
  22655. // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
  22656. if (ie) {
  22657. try {
  22658. self.getDoc();
  22659. } catch (e) {
  22660. n.src = url = domainRelaxUrl;
  22661. }
  22662. }
  22663. self.contentAreaContainer = o.iframeContainer;
  22664. if (o.editorContainer) {
  22665. DOM.get(o.editorContainer).style.display = self.orgDisplay;
  22666. }
  22667. DOM.get(self.id).style.display = 'none';
  22668. DOM.setAttrib(self.id, 'aria-hidden', true);
  22669. if (!url) {
  22670. self.initContentBody();
  22671. }
  22672. elm = n = o = null; // Cleanup
  22673. },
  22674. /**
  22675. * This method get called by the init method ones the iframe is loaded.
  22676. * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
  22677. *
  22678. * @method initContentBody
  22679. * @private
  22680. */
  22681. initContentBody: function(skipWrite) {
  22682. var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText;
  22683. // Restore visibility on target element
  22684. if (!settings.inline) {
  22685. self.getElement().style.visibility = self.orgVisibility;
  22686. }
  22687. // Setup iframe body
  22688. if (!skipWrite && !settings.content_editable) {
  22689. doc.open();
  22690. doc.write(self.iframeHTML);
  22691. doc.close();
  22692. }
  22693. if (settings.content_editable) {
  22694. self.on('remove', function() {
  22695. var bodyEl = this.getBody();
  22696. DOM.removeClass(bodyEl, 'mce-content-body');
  22697. DOM.removeClass(bodyEl, 'mce-edit-focus');
  22698. DOM.setAttrib(bodyEl, 'contentEditable', null);
  22699. });
  22700. DOM.addClass(targetElm, 'mce-content-body');
  22701. self.contentDocument = doc = settings.content_document || document;
  22702. self.contentWindow = settings.content_window || window;
  22703. self.bodyElement = targetElm;
  22704. // Prevent leak in IE
  22705. settings.content_document = settings.content_window = null;
  22706. // TODO: Fix this
  22707. settings.root_name = targetElm.nodeName.toLowerCase();
  22708. }
  22709. // It will not steal focus while setting contentEditable
  22710. body = self.getBody();
  22711. body.disabled = true;
  22712. if (!settings.readonly) {
  22713. if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
  22714. body.style.position = 'relative';
  22715. }
  22716. body.contentEditable = self.getParam('content_editable_state', true);
  22717. }
  22718. body.disabled = false;
  22719. /**
  22720. * Schema instance, enables you to validate elements and it's children.
  22721. *
  22722. * @property schema
  22723. * @type tinymce.html.Schema
  22724. */
  22725. self.schema = new Schema(settings);
  22726. /**
  22727. * DOM instance for the editor.
  22728. *
  22729. * @property dom
  22730. * @type tinymce.dom.DOMUtils
  22731. * @example
  22732. * // Adds a class to all paragraphs within the editor
  22733. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  22734. */
  22735. self.dom = new DOMUtils(doc, {
  22736. keep_values: true,
  22737. url_converter: self.convertURL,
  22738. url_converter_scope: self,
  22739. hex_colors: settings.force_hex_style_colors,
  22740. class_filter: settings.class_filter,
  22741. update_styles: true,
  22742. root_element: settings.content_editable ? self.id : null,
  22743. collect: settings.content_editable,
  22744. schema: self.schema,
  22745. onSetAttrib: function(e) {
  22746. self.fire('SetAttrib', e);
  22747. }
  22748. });
  22749. /**
  22750. * HTML parser will be used when contents is inserted into the editor.
  22751. *
  22752. * @property parser
  22753. * @type tinymce.html.DomParser
  22754. */
  22755. self.parser = new DomParser(settings, self.schema);
  22756. // Convert src and href into data-mce-src, data-mce-href and data-mce-style
  22757. self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
  22758. var i = nodes.length, node, dom = self.dom, value, internalName;
  22759. while (i--) {
  22760. node = nodes[i];
  22761. value = node.attr(name);
  22762. internalName = 'data-mce-' + name;
  22763. // Add internal attribute if we need to we don't on a refresh of the document
  22764. if (!node.attributes.map[internalName]) {
  22765. if (name === "style") {
  22766. node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
  22767. } else if (name === "tabindex") {
  22768. node.attr(internalName, value);
  22769. node.attr(name, null);
  22770. } else {
  22771. node.attr(internalName, self.convertURL(value, name, node.name));
  22772. }
  22773. }
  22774. }
  22775. });
  22776. // Keep scripts from executing
  22777. self.parser.addNodeFilter('script', function(nodes) {
  22778. var i = nodes.length, node;
  22779. while (i--) {
  22780. node = nodes[i];
  22781. node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
  22782. }
  22783. });
  22784. self.parser.addNodeFilter('#cdata', function(nodes) {
  22785. var i = nodes.length, node;
  22786. while (i--) {
  22787. node = nodes[i];
  22788. node.type = 8;
  22789. node.name = '#comment';
  22790. node.value = '[CDATA[' + node.value + ']]';
  22791. }
  22792. });
  22793. self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
  22794. var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
  22795. while (i--) {
  22796. node = nodes[i];
  22797. if (node.isEmpty(nonEmptyElements)) {
  22798. node.empty().append(new Node('br', 1)).shortEnded = true;
  22799. }
  22800. }
  22801. });
  22802. /**
  22803. * DOM serializer for the editor. Will be used when contents is extracted from the editor.
  22804. *
  22805. * @property serializer
  22806. * @type tinymce.dom.Serializer
  22807. * @example
  22808. * // Serializes the first paragraph in the editor into a string
  22809. * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
  22810. */
  22811. self.serializer = new DomSerializer(settings, self);
  22812. /**
  22813. * Selection instance for the editor.
  22814. *
  22815. * @property selection
  22816. * @type tinymce.dom.Selection
  22817. * @example
  22818. * // Sets some contents to the current selection in the editor
  22819. * tinymce.activeEditor.selection.setContent('Some contents');
  22820. *
  22821. * // Gets the current selection
  22822. * alert(tinymce.activeEditor.selection.getContent());
  22823. *
  22824. * // Selects the first paragraph found
  22825. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  22826. */
  22827. self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
  22828. /**
  22829. * Formatter instance.
  22830. *
  22831. * @property formatter
  22832. * @type tinymce.Formatter
  22833. */
  22834. self.formatter = new Formatter(self);
  22835. /**
  22836. * Undo manager instance, responsible for handling undo levels.
  22837. *
  22838. * @property undoManager
  22839. * @type tinymce.UndoManager
  22840. * @example
  22841. * // Undoes the last modification to the editor
  22842. * tinymce.activeEditor.undoManager.undo();
  22843. */
  22844. self.undoManager = new UndoManager(self);
  22845. self.forceBlocks = new ForceBlocks(self);
  22846. self.enterKey = new EnterKey(self);
  22847. self.editorCommands = new EditorCommands(self);
  22848. self.fire('PreInit');
  22849. if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
  22850. doc.body.spellcheck = false; // Gecko
  22851. DOM.setAttrib(body, "spellcheck", "false");
  22852. }
  22853. self.fire('PostRender');
  22854. self.quirks = Quirks(self);
  22855. if (settings.directionality) {
  22856. body.dir = settings.directionality;
  22857. }
  22858. if (settings.nowrap) {
  22859. body.style.whiteSpace = "nowrap";
  22860. }
  22861. if (settings.protect) {
  22862. self.on('BeforeSetContent', function(e) {
  22863. each(settings.protect, function(pattern) {
  22864. e.content = e.content.replace(pattern, function(str) {
  22865. return '<!--mce:protected ' + escape(str) + '-->';
  22866. });
  22867. });
  22868. });
  22869. }
  22870. self.on('SetContent', function() {
  22871. self.addVisual(self.getBody());
  22872. });
  22873. // Remove empty contents
  22874. if (settings.padd_empty_editor) {
  22875. self.on('PostProcess', function(e) {
  22876. e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
  22877. });
  22878. }
  22879. self.load({initial: true, format: 'html'});
  22880. self.startContent = self.getContent({format: 'raw'});
  22881. /**
  22882. * Is set to true after the editor instance has been initialized
  22883. *
  22884. * @property initialized
  22885. * @type Boolean
  22886. * @example
  22887. * function isEditorInitialized(editor) {
  22888. * return editor && editor.initialized;
  22889. * }
  22890. */
  22891. self.initialized = true;
  22892. each(self._pendingNativeEvents, function(name) {
  22893. self.dom.bind(getEventTarget(self, name), name, function(e) {
  22894. self.fire(e.type, e);
  22895. });
  22896. });
  22897. self.fire('init');
  22898. self.focus(true);
  22899. self.nodeChanged({initial: true});
  22900. self.execCallback('init_instance_callback', self);
  22901. // Add editor specific CSS styles
  22902. if (self.contentStyles.length > 0) {
  22903. contentCssText = '';
  22904. each(self.contentStyles, function(style) {
  22905. contentCssText += style + "\r\n";
  22906. });
  22907. self.dom.addStyle(contentCssText);
  22908. }
  22909. // Load specified content CSS last
  22910. each(self.contentCSS, function(cssUrl) {
  22911. if (!self.loadedCSS[cssUrl]) {
  22912. self.dom.loadCSS(cssUrl);
  22913. self.loadedCSS[cssUrl] = true;
  22914. }
  22915. });
  22916. // Handle auto focus
  22917. if (settings.auto_focus) {
  22918. setTimeout(function () {
  22919. var ed = self.editorManager.get(settings.auto_focus);
  22920. ed.selection.select(ed.getBody(), 1);
  22921. ed.selection.collapse(1);
  22922. ed.getBody().focus();
  22923. ed.getWin().focus();
  22924. }, 100);
  22925. }
  22926. // Clean up references for IE
  22927. targetElm = doc = body = null;
  22928. },
  22929. /**
  22930. * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
  22931. * it will also place DOM focus inside the editor.
  22932. *
  22933. * @method focus
  22934. * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
  22935. */
  22936. focus: function(skip_focus) {
  22937. var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
  22938. var controlElm, doc = self.getDoc(), body;
  22939. if (!skip_focus) {
  22940. // Get selected control element
  22941. rng = selection.getRng();
  22942. if (rng.item) {
  22943. controlElm = rng.item(0);
  22944. }
  22945. self._refreshContentEditable();
  22946. // Focus the window iframe
  22947. if (!contentEditable) {
  22948. // WebKit needs this call to fire focusin event properly see #5948
  22949. // But Opera pre Blink engine will produce an empty selection so skip Opera
  22950. if (!Env.opera) {
  22951. self.getBody().focus();
  22952. }
  22953. self.getWin().focus();
  22954. }
  22955. // Focus the body as well since it's contentEditable
  22956. if (isGecko || contentEditable) {
  22957. body = self.getBody();
  22958. // Check for setActive since it doesn't scroll to the element
  22959. if (body.setActive) {
  22960. // IE 11 sometimes throws "Invalid function" then fallback to focus
  22961. try {
  22962. body.setActive();
  22963. } catch (ex) {
  22964. body.focus();
  22965. }
  22966. } else {
  22967. body.focus();
  22968. }
  22969. if (contentEditable) {
  22970. selection.normalize();
  22971. }
  22972. }
  22973. // Restore selected control element
  22974. // This is needed when for example an image is selected within a
  22975. // layer a call to focus will then remove the control selection
  22976. if (controlElm && controlElm.ownerDocument == doc) {
  22977. rng = doc.body.createControlRange();
  22978. rng.addElement(controlElm);
  22979. rng.select();
  22980. }
  22981. }
  22982. if (self.editorManager.activeEditor != self) {
  22983. if ((oed = self.editorManager.activeEditor)) {
  22984. oed.fire('deactivate', {relatedTarget: self});
  22985. }
  22986. self.fire('activate', {relatedTarget: oed});
  22987. }
  22988. self.editorManager.activeEditor = self;
  22989. },
  22990. /**
  22991. * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
  22992. * There new event model is a better way to add callback so this method might be removed in the future.
  22993. *
  22994. * @method execCallback
  22995. * @param {String} name Name of the callback to execute.
  22996. * @return {Object} Return value passed from callback function.
  22997. */
  22998. execCallback: function(name) {
  22999. var self = this, callback = self.settings[name], scope;
  23000. if (!callback) {
  23001. return;
  23002. }
  23003. // Look through lookup
  23004. if (self.callbackLookup && (scope = self.callbackLookup[name])) {
  23005. callback = scope.func;
  23006. scope = scope.scope;
  23007. }
  23008. if (typeof(callback) === 'string') {
  23009. scope = callback.replace(/\.\w+$/, '');
  23010. scope = scope ? resolve(scope) : 0;
  23011. callback = resolve(callback);
  23012. self.callbackLookup = self.callbackLookup || {};
  23013. self.callbackLookup[name] = {func: callback, scope: scope};
  23014. }
  23015. return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
  23016. },
  23017. /**
  23018. * Translates the specified string by replacing variables with language pack items it will also check if there is
  23019. * a key mathcin the input.
  23020. *
  23021. * @method translate
  23022. * @param {String} text String to translate by the language pack data.
  23023. * @return {String} Translated string.
  23024. */
  23025. translate: function(text) {
  23026. var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
  23027. if (!text) {
  23028. return '';
  23029. }
  23030. return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
  23031. return i18n.data[lang + '.' + b] || '{#' + b + '}';
  23032. });
  23033. },
  23034. /**
  23035. * Returns a language pack item by name/key.
  23036. *
  23037. * @method getLang
  23038. * @param {String} name Name/key to get from the language pack.
  23039. * @param {String} defaultVal Optional default value to retrive.
  23040. */
  23041. getLang: function(name, defaultVal) {
  23042. return (
  23043. this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
  23044. (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
  23045. );
  23046. },
  23047. /**
  23048. * Returns a configuration parameter by name.
  23049. *
  23050. * @method getParam
  23051. * @param {String} name Configruation parameter to retrive.
  23052. * @param {String} defaultVal Optional default value to return.
  23053. * @param {String} type Optional type parameter.
  23054. * @return {String} Configuration parameter value or default value.
  23055. * @example
  23056. * // Returns a specific config value from the currently active editor
  23057. * var someval = tinymce.activeEditor.getParam('myvalue');
  23058. *
  23059. * // Returns a specific config value from a specific editor instance by id
  23060. * var someval2 = tinymce.get('my_editor').getParam('myvalue');
  23061. */
  23062. getParam: function(name, defaultVal, type) {
  23063. var value = name in this.settings ? this.settings[name] : defaultVal, output;
  23064. if (type === 'hash') {
  23065. output = {};
  23066. if (typeof(value) === 'string') {
  23067. each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
  23068. value = value.split('=');
  23069. if (value.length > 1) {
  23070. output[trim(value[0])] = trim(value[1]);
  23071. } else {
  23072. output[trim(value[0])] = trim(value);
  23073. }
  23074. });
  23075. } else {
  23076. output = value;
  23077. }
  23078. return output;
  23079. }
  23080. return value;
  23081. },
  23082. /**
  23083. * Distpaches out a onNodeChange event to all observers. This method should be called when you
  23084. * need to update the UI states or element path etc.
  23085. *
  23086. * @method nodeChanged
  23087. */
  23088. nodeChanged: function() {
  23089. var self = this, selection = self.selection, node, parents, root;
  23090. // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
  23091. if (self.initialized && !self.settings.disable_nodechange && !self.settings.readonly) {
  23092. // Get start node
  23093. root = self.getBody();
  23094. node = selection.getStart() || root;
  23095. node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
  23096. // Edge case for <p>|<img></p>
  23097. if (node.nodeName == 'IMG' && selection.isCollapsed()) {
  23098. node = node.parentNode;
  23099. }
  23100. // Get parents and add them to object
  23101. parents = [];
  23102. self.dom.getParent(node, function(node) {
  23103. if (node === root) {
  23104. return true;
  23105. }
  23106. parents.push(node);
  23107. });
  23108. self.fire('NodeChange', {element: node, parents: parents});
  23109. }
  23110. },
  23111. /**
  23112. * Adds a button that later gets created by the theme in the editors toolbars.
  23113. *
  23114. * @method addButton
  23115. * @param {String} name Button name to add.
  23116. * @param {Object} settings Settings object with title, cmd etc.
  23117. * @example
  23118. * // Adds a custom button to the editor that inserts contents when clicked
  23119. * tinymce.init({
  23120. * ...
  23121. *
  23122. * toolbar: 'example'
  23123. *
  23124. * setup: function(ed) {
  23125. * ed.addButton('example', {
  23126. * title: 'My title',
  23127. * image: '../js/tinymce/plugins/example/img/example.gif',
  23128. * onclick: function() {
  23129. * ed.insertContent('Hello world!!');
  23130. * }
  23131. * });
  23132. * }
  23133. * });
  23134. */
  23135. addButton: function(name, settings) {
  23136. var self = this;
  23137. if (settings.cmd) {
  23138. settings.onclick = function() {
  23139. self.execCommand(settings.cmd);
  23140. };
  23141. }
  23142. if (!settings.text && !settings.icon) {
  23143. settings.icon = name;
  23144. }
  23145. self.buttons = self.buttons || {};
  23146. settings.tooltip = settings.tooltip || settings.title;
  23147. self.buttons[name] = settings;
  23148. },
  23149. /**
  23150. * Adds a menu item to be used in the menus of the theme. There might be multiple instances
  23151. * of this menu item for example it might be used in the main menus of the theme but also in
  23152. * the context menu so make sure that it's self contained and supports multiple instances.
  23153. *
  23154. * @method addMenuItem
  23155. * @param {String} name Menu item name to add.
  23156. * @param {Object} settings Settings object with title, cmd etc.
  23157. * @example
  23158. * // Adds a custom menu item to the editor that inserts contents when clicked
  23159. * // The context option allows you to add the menu item to an existing default menu
  23160. * tinymce.init({
  23161. * ...
  23162. *
  23163. * setup: function(ed) {
  23164. * ed.addMenuItem('example', {
  23165. * text: 'My menu item',
  23166. * context: 'tools',
  23167. * onclick: function() {
  23168. * ed.insertContent('Hello world!!');
  23169. * }
  23170. * });
  23171. * }
  23172. * });
  23173. */
  23174. addMenuItem: function(name, settings) {
  23175. var self = this;
  23176. if (settings.cmd) {
  23177. settings.onclick = function() {
  23178. self.execCommand(settings.cmd);
  23179. };
  23180. }
  23181. self.menuItems = self.menuItems || {};
  23182. self.menuItems[name] = settings;
  23183. },
  23184. /**
  23185. * Adds a custom command to the editor, you can also override existing commands with this method.
  23186. * The command that you add can be executed with execCommand.
  23187. *
  23188. * @method addCommand
  23189. * @param {String} name Command name to add/override.
  23190. * @param {addCommandCallback} callback Function to execute when the command occurs.
  23191. * @param {Object} scope Optional scope to execute the function in.
  23192. * @example
  23193. * // Adds a custom command that later can be executed using execCommand
  23194. * tinymce.init({
  23195. * ...
  23196. *
  23197. * setup: function(ed) {
  23198. * // Register example command
  23199. * ed.addCommand('mycommand', function(ui, v) {
  23200. * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
  23201. * });
  23202. * }
  23203. * });
  23204. */
  23205. addCommand: function(name, callback, scope) {
  23206. /**
  23207. * Callback function that gets called when a command is executed.
  23208. *
  23209. * @callback addCommandCallback
  23210. * @param {Boolean} ui Display UI state true/false.
  23211. * @param {Object} value Optional value for command.
  23212. * @return {Boolean} True/false state if the command was handled or not.
  23213. */
  23214. this.execCommands[name] = {func: callback, scope: scope || this};
  23215. },
  23216. /**
  23217. * Adds a custom query state command to the editor, you can also override existing commands with this method.
  23218. * The command that you add can be executed with queryCommandState function.
  23219. *
  23220. * @method addQueryStateHandler
  23221. * @param {String} name Command name to add/override.
  23222. * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
  23223. * @param {Object} scope Optional scope to execute the function in.
  23224. */
  23225. addQueryStateHandler: function(name, callback, scope) {
  23226. /**
  23227. * Callback function that gets called when a queryCommandState is executed.
  23228. *
  23229. * @callback addQueryStateHandlerCallback
  23230. * @return {Boolean} True/false state if the command is enabled or not like is it bold.
  23231. */
  23232. this.queryStateCommands[name] = {func: callback, scope: scope || this};
  23233. },
  23234. /**
  23235. * Adds a custom query value command to the editor, you can also override existing commands with this method.
  23236. * The command that you add can be executed with queryCommandValue function.
  23237. *
  23238. * @method addQueryValueHandler
  23239. * @param {String} name Command name to add/override.
  23240. * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
  23241. * @param {Object} scope Optional scope to execute the function in.
  23242. */
  23243. addQueryValueHandler: function(name, callback, scope) {
  23244. /**
  23245. * Callback function that gets called when a queryCommandValue is executed.
  23246. *
  23247. * @callback addQueryValueHandlerCallback
  23248. * @return {Object} Value of the command or undefined.
  23249. */
  23250. this.queryValueCommands[name] = {func: callback, scope: scope || this};
  23251. },
  23252. /**
  23253. * Adds a keyboard shortcut for some command or function.
  23254. *
  23255. * @method addShortcut
  23256. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  23257. * @param {String} desc Text description for the command.
  23258. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  23259. * @param {Object} sc Optional scope to execute the function in.
  23260. * @return {Boolean} true/false state if the shortcut was added or not.
  23261. */
  23262. addShortcut: function(pattern, desc, cmdFunc, scope) {
  23263. this.shortcuts.add(pattern, desc, cmdFunc, scope);
  23264. },
  23265. /**
  23266. * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
  23267. * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
  23268. * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
  23269. * return true it will handle the command as a internal browser command.
  23270. *
  23271. * @method execCommand
  23272. * @param {String} cmd Command name to execute, for example mceLink or Bold.
  23273. * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
  23274. * @param {mixed} value Optional command value, this can be anything.
  23275. * @param {Object} a Optional arguments object.
  23276. */
  23277. execCommand: function(cmd, ui, value, args) {
  23278. var self = this, state = 0, cmdItem;
  23279. if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
  23280. self.focus();
  23281. }
  23282. args = extend({}, args);
  23283. args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
  23284. if (args.isDefaultPrevented()) {
  23285. return false;
  23286. }
  23287. // Registred commands
  23288. if ((cmdItem = self.execCommands[cmd])) {
  23289. // Fall through on true
  23290. if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
  23291. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23292. return true;
  23293. }
  23294. }
  23295. // Plugin commands
  23296. each(self.plugins, function(p) {
  23297. if (p.execCommand && p.execCommand(cmd, ui, value)) {
  23298. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23299. state = true;
  23300. return false;
  23301. }
  23302. });
  23303. if (state) {
  23304. return state;
  23305. }
  23306. // Theme commands
  23307. if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
  23308. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23309. return true;
  23310. }
  23311. // Editor commands
  23312. if (self.editorCommands.execCommand(cmd, ui, value)) {
  23313. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23314. return true;
  23315. }
  23316. // Browser commands
  23317. self.getDoc().execCommand(cmd, ui, value);
  23318. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23319. },
  23320. /**
  23321. * Returns a command specific state, for example if bold is enabled or not.
  23322. *
  23323. * @method queryCommandState
  23324. * @param {string} cmd Command to query state from.
  23325. * @return {Boolean} Command specific state, for example if bold is enabled or not.
  23326. */
  23327. queryCommandState: function(cmd) {
  23328. var self = this, queryItem, returnVal;
  23329. // Is hidden then return undefined
  23330. if (self._isHidden()) {
  23331. return;
  23332. }
  23333. // Registred commands
  23334. if ((queryItem = self.queryStateCommands[cmd])) {
  23335. returnVal = queryItem.func.call(queryItem.scope);
  23336. // Fall though on true
  23337. if (returnVal !== true) {
  23338. return returnVal;
  23339. }
  23340. }
  23341. // Editor commands
  23342. returnVal = self.editorCommands.queryCommandState(cmd);
  23343. if (returnVal !== -1) {
  23344. return returnVal;
  23345. }
  23346. // Browser commands
  23347. try {
  23348. return self.getDoc().queryCommandState(cmd);
  23349. } catch (ex) {
  23350. // Fails sometimes see bug: 1896577
  23351. }
  23352. },
  23353. /**
  23354. * Returns a command specific value, for example the current font size.
  23355. *
  23356. * @method queryCommandValue
  23357. * @param {string} cmd Command to query value from.
  23358. * @return {Object} Command specific value, for example the current font size.
  23359. */
  23360. queryCommandValue: function(cmd) {
  23361. var self = this, queryItem, returnVal;
  23362. // Is hidden then return undefined
  23363. if (self._isHidden()) {
  23364. return;
  23365. }
  23366. // Registred commands
  23367. if ((queryItem = self.queryValueCommands[cmd])) {
  23368. returnVal = queryItem.func.call(queryItem.scope);
  23369. // Fall though on true
  23370. if (returnVal !== true) {
  23371. return returnVal;
  23372. }
  23373. }
  23374. // Editor commands
  23375. returnVal = self.editorCommands.queryCommandValue(cmd);
  23376. if (returnVal !== undefined) {
  23377. return returnVal;
  23378. }
  23379. // Browser commands
  23380. try {
  23381. return self.getDoc().queryCommandValue(cmd);
  23382. } catch (ex) {
  23383. // Fails sometimes see bug: 1896577
  23384. }
  23385. },
  23386. /**
  23387. * Shows the editor and hides any textarea/div that the editor is supposed to replace.
  23388. *
  23389. * @method show
  23390. */
  23391. show: function() {
  23392. var self = this;
  23393. DOM.show(self.getContainer());
  23394. DOM.hide(self.id);
  23395. self.load();
  23396. self.fire('show');
  23397. },
  23398. /**
  23399. * Hides the editor and shows any textarea/div that the editor is supposed to replace.
  23400. *
  23401. * @method hide
  23402. */
  23403. hide: function() {
  23404. var self = this, doc = self.getDoc();
  23405. // Fixed bug where IE has a blinking cursor left from the editor
  23406. if (ie && doc && !self.inline) {
  23407. doc.execCommand('SelectAll');
  23408. }
  23409. // We must save before we hide so Safari doesn't crash
  23410. self.save();
  23411. // defer the call to hide to prevent an IE9 crash #4921
  23412. DOM.hide(self.getContainer());
  23413. DOM.setStyle(self.id, 'display', self.orgDisplay);
  23414. self.fire('hide');
  23415. },
  23416. /**
  23417. * Returns true/false if the editor is hidden or not.
  23418. *
  23419. * @method isHidden
  23420. * @return {Boolean} True/false if the editor is hidden or not.
  23421. */
  23422. isHidden: function() {
  23423. return !DOM.isHidden(this.id);
  23424. },
  23425. /**
  23426. * Sets the progress state, this will display a throbber/progess for the editor.
  23427. * This is ideal for asycronous operations like an AJAX save call.
  23428. *
  23429. * @method setProgressState
  23430. * @param {Boolean} state Boolean state if the progress should be shown or hidden.
  23431. * @param {Number} time Optional time to wait before the progress gets shown.
  23432. * @return {Boolean} Same as the input state.
  23433. * @example
  23434. * // Show progress for the active editor
  23435. * tinymce.activeEditor.setProgressState(true);
  23436. *
  23437. * // Hide progress for the active editor
  23438. * tinymce.activeEditor.setProgressState(false);
  23439. *
  23440. * // Show progress after 3 seconds
  23441. * tinymce.activeEditor.setProgressState(true, 3000);
  23442. */
  23443. setProgressState: function(state, time) {
  23444. this.fire('ProgressState', {state: state, time: time});
  23445. },
  23446. /**
  23447. * Loads contents from the textarea or div element that got converted into an editor instance.
  23448. * This method will move the contents from that textarea or div into the editor by using setContent
  23449. * so all events etc that method has will get dispatched as well.
  23450. *
  23451. * @method load
  23452. * @param {Object} args Optional content object, this gets passed around through the whole load process.
  23453. * @return {String} HTML string that got set into the editor.
  23454. */
  23455. load: function(args) {
  23456. var self = this, elm = self.getElement(), html;
  23457. if (elm) {
  23458. args = args || {};
  23459. args.load = true;
  23460. html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
  23461. args.element = elm;
  23462. if (!args.no_events) {
  23463. self.fire('LoadContent', args);
  23464. }
  23465. args.element = elm = null;
  23466. return html;
  23467. }
  23468. },
  23469. /**
  23470. * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
  23471. * This method will move the HTML contents from the editor into that textarea or div by getContent
  23472. * so all events etc that method has will get dispatched as well.
  23473. *
  23474. * @method save
  23475. * @param {Object} args Optional content object, this gets passed around through the whole save process.
  23476. * @return {String} HTML string that got set into the textarea/div.
  23477. */
  23478. save: function(args) {
  23479. var self = this, elm = self.getElement(), html, form;
  23480. if (!elm || !self.initialized) {
  23481. return;
  23482. }
  23483. args = args || {};
  23484. args.save = true;
  23485. args.element = elm;
  23486. html = args.content = self.getContent(args);
  23487. if (!args.no_events) {
  23488. self.fire('SaveContent', args);
  23489. }
  23490. html = args.content;
  23491. if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
  23492. // Update DIV element when not in inline mode
  23493. if (!self.inline) {
  23494. elm.innerHTML = html;
  23495. }
  23496. // Update hidden form element
  23497. if ((form = DOM.getParent(self.id, 'form'))) {
  23498. each(form.elements, function(elm) {
  23499. if (elm.name == self.id) {
  23500. elm.value = html;
  23501. return false;
  23502. }
  23503. });
  23504. }
  23505. } else {
  23506. elm.value = html;
  23507. }
  23508. args.element = elm = null;
  23509. if (args.set_dirty !== false) {
  23510. self.isNotDirty = true;
  23511. }
  23512. return html;
  23513. },
  23514. /**
  23515. * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
  23516. * the different cleanup rules options.
  23517. *
  23518. * @method setContent
  23519. * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
  23520. * @param {Object} args Optional content object, this gets passed around through the whole set process.
  23521. * @return {String} HTML string that got set into the editor.
  23522. * @example
  23523. * // Sets the HTML contents of the activeEditor editor
  23524. * tinymce.activeEditor.setContent('<span>some</span> html');
  23525. *
  23526. * // Sets the raw contents of the activeEditor editor
  23527. * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
  23528. *
  23529. * // Sets the content of a specific editor (my_editor in this example)
  23530. * tinymce.get('my_editor').setContent(data);
  23531. *
  23532. * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
  23533. * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
  23534. */
  23535. setContent: function(content, args) {
  23536. var self = this, body = self.getBody(), forcedRootBlockName;
  23537. // Setup args object
  23538. args = args || {};
  23539. args.format = args.format || 'html';
  23540. args.set = true;
  23541. args.content = content;
  23542. // Do preprocessing
  23543. if (!args.no_events) {
  23544. self.fire('BeforeSetContent', args);
  23545. }
  23546. content = args.content;
  23547. // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
  23548. // It will also be impossible to place the caret in the editor unless there is a BR element present
  23549. if (content.length === 0 || /^\s+$/.test(content)) {
  23550. forcedRootBlockName = self.settings.forced_root_block;
  23551. // Check if forcedRootBlock is configured and that the block is a valid child of the body
  23552. if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
  23553. // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
  23554. content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
  23555. content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
  23556. } else if (!ie) {
  23557. // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
  23558. content = '<br data-mce-bogus="1">';
  23559. }
  23560. body.innerHTML = content;
  23561. self.fire('SetContent', args);
  23562. } else {
  23563. // Parse and serialize the html
  23564. if (args.format !== 'raw') {
  23565. content = new Serializer({}, self.schema).serialize(
  23566. self.parser.parse(content, {isRootContent: true})
  23567. );
  23568. }
  23569. // Set the new cleaned contents to the editor
  23570. args.content = trim(content);
  23571. self.dom.setHTML(body, args.content);
  23572. // Do post processing
  23573. if (!args.no_events) {
  23574. self.fire('SetContent', args);
  23575. }
  23576. // Don't normalize selection if the focused element isn't the body in
  23577. // content editable mode since it will steal focus otherwise
  23578. /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
  23579. self.selection.normalize();
  23580. }*/
  23581. }
  23582. return args.content;
  23583. },
  23584. /**
  23585. * Gets the content from the editor instance, this will cleanup the content before it gets returned using
  23586. * the different cleanup rules options.
  23587. *
  23588. * @method getContent
  23589. * @param {Object} args Optional content object, this gets passed around through the whole get process.
  23590. * @return {String} Cleaned content string, normally HTML contents.
  23591. * @example
  23592. * // Get the HTML contents of the currently active editor
  23593. * console.debug(tinymce.activeEditor.getContent());
  23594. *
  23595. * // Get the raw contents of the currently active editor
  23596. * tinymce.activeEditor.getContent({format: 'raw'});
  23597. *
  23598. * // Get content of a specific editor:
  23599. * tinymce.get('content id').getContent()
  23600. */
  23601. getContent: function(args) {
  23602. var self = this, content, body = self.getBody();
  23603. // Setup args object
  23604. args = args || {};
  23605. args.format = args.format || 'html';
  23606. args.get = true;
  23607. args.getInner = true;
  23608. // Do preprocessing
  23609. if (!args.no_events) {
  23610. self.fire('BeforeGetContent', args);
  23611. }
  23612. // Get raw contents or by default the cleaned contents
  23613. if (args.format == 'raw') {
  23614. content = body.innerHTML;
  23615. } else if (args.format == 'text') {
  23616. content = body.innerText || body.textContent;
  23617. } else {
  23618. content = self.serializer.serialize(body, args);
  23619. }
  23620. // Trim whitespace in beginning/end of HTML
  23621. if (args.format != 'text') {
  23622. args.content = trim(content);
  23623. } else {
  23624. args.content = content;
  23625. }
  23626. // Do post processing
  23627. if (!args.no_events) {
  23628. self.fire('GetContent', args);
  23629. }
  23630. return args.content;
  23631. },
  23632. /**
  23633. * Inserts content at caret position.
  23634. *
  23635. * @method insertContent
  23636. * @param {String} content Content to insert.
  23637. */
  23638. insertContent: function(content) {
  23639. this.execCommand('mceInsertContent', false, content);
  23640. },
  23641. /**
  23642. * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  23643. *
  23644. * @method isDirty
  23645. * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  23646. * @example
  23647. * if (tinymce.activeEditor.isDirty())
  23648. * alert("You must save your contents.");
  23649. */
  23650. isDirty: function() {
  23651. return !this.isNotDirty;
  23652. },
  23653. /**
  23654. * Returns the editors container element. The container element wrappes in
  23655. * all the elements added to the page for the editor. Such as UI, iframe etc.
  23656. *
  23657. * @method getContainer
  23658. * @return {Element} HTML DOM element for the editor container.
  23659. */
  23660. getContainer: function() {
  23661. var self = this;
  23662. if (!self.container) {
  23663. self.container = DOM.get(self.editorContainer || self.id + '_parent');
  23664. }
  23665. return self.container;
  23666. },
  23667. /**
  23668. * Returns the editors content area container element. The this element is the one who
  23669. * holds the iframe or the editable element.
  23670. *
  23671. * @method getContentAreaContainer
  23672. * @return {Element} HTML DOM element for the editor area container.
  23673. */
  23674. getContentAreaContainer: function() {
  23675. return this.contentAreaContainer;
  23676. },
  23677. /**
  23678. * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
  23679. *
  23680. * @method getElement
  23681. * @return {Element} HTML DOM element for the replaced element.
  23682. */
  23683. getElement: function() {
  23684. return DOM.get(this.settings.content_element || this.id);
  23685. },
  23686. /**
  23687. * Returns the iframes window object.
  23688. *
  23689. * @method getWin
  23690. * @return {Window} Iframe DOM window object.
  23691. */
  23692. getWin: function() {
  23693. var self = this, elm;
  23694. if (!self.contentWindow) {
  23695. elm = DOM.get(self.id + "_ifr");
  23696. if (elm) {
  23697. self.contentWindow = elm.contentWindow;
  23698. }
  23699. }
  23700. return self.contentWindow;
  23701. },
  23702. /**
  23703. * Returns the iframes document object.
  23704. *
  23705. * @method getDoc
  23706. * @return {Document} Iframe DOM document object.
  23707. */
  23708. getDoc: function() {
  23709. var self = this, win;
  23710. if (!self.contentDocument) {
  23711. win = self.getWin();
  23712. if (win) {
  23713. self.contentDocument = win.document;
  23714. }
  23715. }
  23716. return self.contentDocument;
  23717. },
  23718. /**
  23719. * Returns the iframes body element.
  23720. *
  23721. * @method getBody
  23722. * @return {Element} Iframe body element.
  23723. */
  23724. getBody: function() {
  23725. return this.bodyElement || this.getDoc().body;
  23726. },
  23727. /**
  23728. * URL converter function this gets executed each time a user adds an img, a or
  23729. * any other element that has a URL in it. This will be called both by the DOM and HTML
  23730. * manipulation functions.
  23731. *
  23732. * @method convertURL
  23733. * @param {string} url URL to convert.
  23734. * @param {string} name Attribute name src, href etc.
  23735. * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
  23736. * @return {string} Converted URL string.
  23737. */
  23738. convertURL: function(url, name, elm) {
  23739. var self = this, settings = self.settings;
  23740. // Use callback instead
  23741. if (settings.urlconverter_callback) {
  23742. return self.execCallback('urlconverter_callback', url, elm, true, name);
  23743. }
  23744. // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
  23745. if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
  23746. return url;
  23747. }
  23748. // Convert to relative
  23749. if (settings.relative_urls) {
  23750. return self.documentBaseURI.toRelative(url);
  23751. }
  23752. // Convert to absolute
  23753. url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
  23754. return url;
  23755. },
  23756. /**
  23757. * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
  23758. *
  23759. * @method addVisual
  23760. * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
  23761. */
  23762. addVisual: function(elm) {
  23763. var self = this, settings = self.settings, dom = self.dom, cls;
  23764. elm = elm || self.getBody();
  23765. if (self.hasVisual === undefined) {
  23766. self.hasVisual = settings.visual;
  23767. }
  23768. each(dom.select('table,a', elm), function(elm) {
  23769. var value;
  23770. switch (elm.nodeName) {
  23771. case 'TABLE':
  23772. cls = settings.visual_table_class || 'mce-item-table';
  23773. value = dom.getAttrib(elm, 'border');
  23774. if (!value || value == '0') {
  23775. if (self.hasVisual) {
  23776. dom.addClass(elm, cls);
  23777. } else {
  23778. dom.removeClass(elm, cls);
  23779. }
  23780. }
  23781. return;
  23782. case 'A':
  23783. if (!dom.getAttrib(elm, 'href', false)) {
  23784. value = dom.getAttrib(elm, 'name') || elm.id;
  23785. cls = settings.visual_anchor_class || 'mce-item-anchor';
  23786. if (value) {
  23787. if (self.hasVisual) {
  23788. dom.addClass(elm, cls);
  23789. } else {
  23790. dom.removeClass(elm, cls);
  23791. }
  23792. }
  23793. }
  23794. return;
  23795. }
  23796. });
  23797. self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
  23798. },
  23799. /**
  23800. * Removes the editor from the dom and tinymce collection.
  23801. *
  23802. * @method remove
  23803. */
  23804. remove: function() {
  23805. var self = this;
  23806. if (!self.removed) {
  23807. self.removed = 1;
  23808. self.save();
  23809. // Remove any hidden input
  23810. if (self.hasHiddenInput) {
  23811. DOM.remove(self.getElement().nextSibling);
  23812. }
  23813. if (!self.inline) {
  23814. // IE 9 has a bug where the selection stops working if you place the
  23815. // caret inside the editor then remove the iframe
  23816. if (ie && ie < 10) {
  23817. self.getDoc().execCommand('SelectAll', false, null);
  23818. }
  23819. DOM.setStyle(self.id, 'display', self.orgDisplay);
  23820. self.getBody().onload = null; // Prevent #6816
  23821. // Don't clear the window or document if content editable
  23822. // is enabled since other instances might still be present
  23823. Event.unbind(self.getWin());
  23824. Event.unbind(self.getDoc());
  23825. }
  23826. var elm = self.getContainer();
  23827. Event.unbind(self.getBody());
  23828. Event.unbind(elm);
  23829. self.fire('remove');
  23830. self.editorManager.remove(self);
  23831. DOM.remove(elm);
  23832. self.destroy();
  23833. }
  23834. },
  23835. bindNative: function(name) {
  23836. var self = this;
  23837. if (self.settings.readonly) {
  23838. return;
  23839. }
  23840. if (self.initialized) {
  23841. self.dom.bind(getEventTarget(self, name), name, function(e) {
  23842. self.fire(name, e);
  23843. });
  23844. } else {
  23845. if (!self._pendingNativeEvents) {
  23846. self._pendingNativeEvents = [name];
  23847. } else {
  23848. self._pendingNativeEvents.push(name);
  23849. }
  23850. }
  23851. },
  23852. unbindNative: function(name) {
  23853. var self = this;
  23854. if (self.initialized) {
  23855. self.dom.unbind(name);
  23856. }
  23857. },
  23858. /**
  23859. * Destroys the editor instance by removing all events, element references or other resources
  23860. * that could leak memory. This method will be called automatically when the page is unloaded
  23861. * but you can also call it directly if you know what you are doing.
  23862. *
  23863. * @method destroy
  23864. * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
  23865. */
  23866. destroy: function(automatic) {
  23867. var self = this, form;
  23868. // One time is enough
  23869. if (self.destroyed) {
  23870. return;
  23871. }
  23872. // If user manually calls destroy and not remove
  23873. // Users seems to have logic that calls destroy instead of remove
  23874. if (!automatic && !self.removed) {
  23875. self.remove();
  23876. return;
  23877. }
  23878. // We must unbind on Gecko since it would otherwise produce the pesky "attempt
  23879. // to run compile-and-go script on a cleared scope" message
  23880. if (automatic && isGecko) {
  23881. Event.unbind(self.getDoc());
  23882. Event.unbind(self.getWin());
  23883. Event.unbind(self.getBody());
  23884. }
  23885. if (!automatic) {
  23886. self.editorManager.off('beforeunload', self._beforeUnload);
  23887. // Manual destroy
  23888. if (self.theme && self.theme.destroy) {
  23889. self.theme.destroy();
  23890. }
  23891. // Destroy controls, selection and dom
  23892. self.selection.destroy();
  23893. self.dom.destroy();
  23894. }
  23895. form = self.formElement;
  23896. if (form) {
  23897. if (form._mceOldSubmit) {
  23898. form.submit = form._mceOldSubmit;
  23899. form._mceOldSubmit = null;
  23900. }
  23901. DOM.unbind(form, 'submit reset', self.formEventDelegate);
  23902. }
  23903. self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
  23904. self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null;
  23905. if (self.selection) {
  23906. self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
  23907. }
  23908. self.destroyed = 1;
  23909. },
  23910. // Internal functions
  23911. _refreshContentEditable: function() {
  23912. var self = this, body, parent;
  23913. // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
  23914. if (self._isHidden()) {
  23915. body = self.getBody();
  23916. parent = body.parentNode;
  23917. parent.removeChild(body);
  23918. parent.appendChild(body);
  23919. body.focus();
  23920. }
  23921. },
  23922. _isHidden: function() {
  23923. var sel;
  23924. if (!isGecko) {
  23925. return 0;
  23926. }
  23927. // Weird, wheres that cursor selection?
  23928. sel = this.selection.getSel();
  23929. return (!sel || !sel.rangeCount || sel.rangeCount === 0);
  23930. }
  23931. };
  23932. extend(Editor.prototype, Observable);
  23933. return Editor;
  23934. });
  23935. // Included from: js/tinymce/classes/util/I18n.js
  23936. /**
  23937. * I18n.js
  23938. *
  23939. * Copyright, Moxiecode Systems AB
  23940. * Released under LGPL License.
  23941. *
  23942. * License: http://www.tinymce.com/license
  23943. * Contributing: http://www.tinymce.com/contributing
  23944. */
  23945. /**
  23946. * I18n class that handles translation of TinyMCE UI.
  23947. * Uses po style with csharp style parameters.
  23948. *
  23949. * @class tinymce.util.I18n
  23950. */
  23951. define("tinymce/util/I18n", [], function() {
  23952. "use strict";
  23953. var data = {};
  23954. return {
  23955. /**
  23956. * Property gets set to true if a RTL language pack was loaded.
  23957. *
  23958. * @property rtl
  23959. * @type Boolean
  23960. */
  23961. rtl: false,
  23962. /**
  23963. * Adds translations for a specific language code.
  23964. *
  23965. * @method add
  23966. * @param {String} code Language code like sv_SE.
  23967. * @param {Array} items Name/value array with English en_US to sv_SE.
  23968. */
  23969. add: function(code, items) {
  23970. for (var name in items) {
  23971. data[name] = items[name];
  23972. }
  23973. this.rtl = this.rtl || data._dir === 'rtl';
  23974. },
  23975. /**
  23976. * Translates the specified text.
  23977. *
  23978. * It has a few formats:
  23979. * I18n.translate("Text");
  23980. * I18n.translate(["Text {0}/{1}", 0, 1]);
  23981. * I18n.translate({raw: "Raw string"});
  23982. *
  23983. * @method translate
  23984. * @param {String/Object/Array} text Text to translate.
  23985. * @return {String} String that got translated.
  23986. */
  23987. translate: function(text) {
  23988. if (typeof(text) == "undefined") {
  23989. return text;
  23990. }
  23991. if (typeof(text) != "string" && text.raw) {
  23992. return text.raw;
  23993. }
  23994. if (text.push) {
  23995. var values = text.slice(1);
  23996. text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
  23997. return values[match2];
  23998. });
  23999. }
  24000. return data[text] || text;
  24001. },
  24002. data: data
  24003. };
  24004. });
  24005. // Included from: js/tinymce/classes/FocusManager.js
  24006. /**
  24007. * FocusManager.js
  24008. *
  24009. * Copyright, Moxiecode Systems AB
  24010. * Released under LGPL License.
  24011. *
  24012. * License: http://www.tinymce.com/license
  24013. * Contributing: http://www.tinymce.com/contributing
  24014. */
  24015. /**
  24016. * This class manages the focus/blur state of the editor. This class is needed since some
  24017. * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
  24018. *
  24019. * This class will fire two events focus and blur on the editor instances that got affected.
  24020. * It will also handle the restore of selection when the focus is lost and returned.
  24021. *
  24022. * @class tinymce.FocusManager
  24023. */
  24024. define("tinymce/FocusManager", [
  24025. "tinymce/dom/DOMUtils",
  24026. "tinymce/Env"
  24027. ], function(DOMUtils, Env) {
  24028. var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
  24029. /**
  24030. * Constructs a new focus manager instance.
  24031. *
  24032. * @constructor FocusManager
  24033. * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
  24034. */
  24035. function FocusManager(editorManager) {
  24036. function getActiveElement() {
  24037. try {
  24038. return document.activeElement;
  24039. } catch (ex) {
  24040. // IE sometimes fails to get the activeElement when resizing table
  24041. // TODO: Investigate this
  24042. return document.body;
  24043. }
  24044. }
  24045. // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
  24046. // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
  24047. function createBookmark(dom, rng) {
  24048. if (rng && rng.startContainer) {
  24049. // Verify that the range is within the root of the editor
  24050. if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
  24051. return;
  24052. }
  24053. return {
  24054. startContainer: rng.startContainer,
  24055. startOffset: rng.startOffset,
  24056. endContainer: rng.endContainer,
  24057. endOffset: rng.endOffset
  24058. };
  24059. }
  24060. return rng;
  24061. }
  24062. function bookmarkToRng(editor, bookmark) {
  24063. var rng;
  24064. if (bookmark.startContainer) {
  24065. rng = editor.getDoc().createRange();
  24066. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  24067. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  24068. } else {
  24069. rng = bookmark;
  24070. }
  24071. return rng;
  24072. }
  24073. function isUIElement(elm) {
  24074. return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
  24075. }
  24076. function registerEvents(e) {
  24077. var editor = e.editor;
  24078. editor.on('init', function() {
  24079. // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
  24080. if (editor.inline || Env.ie) {
  24081. // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
  24082. editor.on('nodechange keyup', function() {
  24083. var node = document.activeElement;
  24084. // IE 11 reports active element as iframe not body of iframe
  24085. if (node && node.id == editor.id + '_ifr') {
  24086. node = editor.getBody();
  24087. }
  24088. if (editor.dom.isChildOf(node, editor.getBody())) {
  24089. editor.lastRng = editor.selection.getRng();
  24090. }
  24091. });
  24092. // Handles the issue with WebKit not retaining selection within inline document
  24093. // If the user releases the mouse out side the body since a mouse up event wont occur on the body
  24094. if (Env.webkit && !selectionChangeHandler) {
  24095. selectionChangeHandler = function() {
  24096. var activeEditor = editorManager.activeEditor;
  24097. if (activeEditor && activeEditor.selection) {
  24098. var rng = activeEditor.selection.getRng();
  24099. // Store when it's non collapsed
  24100. if (rng && !rng.collapsed) {
  24101. editor.lastRng = rng;
  24102. }
  24103. }
  24104. };
  24105. DOM.bind(document, 'selectionchange', selectionChangeHandler);
  24106. }
  24107. }
  24108. });
  24109. editor.on('setcontent', function() {
  24110. editor.lastRng = null;
  24111. });
  24112. // Remove last selection bookmark on mousedown see #6305
  24113. editor.on('mousedown', function() {
  24114. editor.selection.lastFocusBookmark = null;
  24115. });
  24116. editor.on('focusin', function() {
  24117. var focusedEditor = editorManager.focusedEditor;
  24118. if (editor.selection.lastFocusBookmark) {
  24119. editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
  24120. editor.selection.lastFocusBookmark = null;
  24121. }
  24122. if (focusedEditor != editor) {
  24123. if (focusedEditor) {
  24124. focusedEditor.fire('blur', {focusedEditor: editor});
  24125. }
  24126. editorManager.activeEditor = editor;
  24127. editorManager.focusedEditor = editor;
  24128. editor.fire('focus', {blurredEditor: focusedEditor});
  24129. editor.focus(true);
  24130. }
  24131. editor.lastRng = null;
  24132. });
  24133. editor.on('focusout', function() {
  24134. window.setTimeout(function() {
  24135. var focusedEditor = editorManager.focusedEditor;
  24136. // Still the same editor the the blur was outside any editor UI
  24137. if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
  24138. editor.fire('blur', {focusedEditor: null});
  24139. editorManager.focusedEditor = null;
  24140. // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
  24141. if (editor.selection) {
  24142. editor.selection.lastFocusBookmark = null;
  24143. }
  24144. }
  24145. }, 0);
  24146. });
  24147. // Check if focus is moved to an element outside the active editor by checking if the target node
  24148. // isn't within the body of the activeEditor nor a UI element such as a dialog child control
  24149. if (!documentFocusInHandler) {
  24150. documentFocusInHandler = function(e) {
  24151. var activeEditor = editorManager.activeEditor;
  24152. if (activeEditor && e.target.ownerDocument == document) {
  24153. // Check to make sure we have a valid selection
  24154. if (activeEditor.selection) {
  24155. activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
  24156. }
  24157. // Fire a blur event if the element isn't a UI element
  24158. if (!isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
  24159. activeEditor.fire('blur', {focusedEditor: null});
  24160. editorManager.focusedEditor = null;
  24161. }
  24162. }
  24163. };
  24164. DOM.bind(document, 'focusin', documentFocusInHandler);
  24165. }
  24166. // Handle edge case when user starts the selection inside the editor and releases
  24167. // the mouse outside the editor producing a new selection. This weird workaround is needed since
  24168. // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
  24169. if (editor.inline && !documentMouseUpHandler) {
  24170. documentMouseUpHandler = function(e) {
  24171. var activeEditor = editorManager.activeEditor;
  24172. if (activeEditor.inline && !activeEditor.dom.isChildOf(e.target, activeEditor.getBody())) {
  24173. var rng = activeEditor.selection.getRng();
  24174. if (!rng.collapsed) {
  24175. activeEditor.lastRng = rng;
  24176. }
  24177. }
  24178. };
  24179. DOM.bind(document, 'mouseup', documentMouseUpHandler);
  24180. }
  24181. }
  24182. function unregisterDocumentEvents(e) {
  24183. if (editorManager.focusedEditor == e.editor) {
  24184. editorManager.focusedEditor = null;
  24185. }
  24186. if (!editorManager.activeEditor) {
  24187. DOM.unbind(document, 'selectionchange', selectionChangeHandler);
  24188. DOM.unbind(document, 'focusin', documentFocusInHandler);
  24189. DOM.unbind(document, 'mouseup', documentMouseUpHandler);
  24190. selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
  24191. }
  24192. }
  24193. editorManager.on('AddEditor', registerEvents);
  24194. editorManager.on('RemoveEditor', unregisterDocumentEvents);
  24195. }
  24196. /**
  24197. * Returns true if the specified element is part of the UI for example an button or text input.
  24198. *
  24199. * @method isEditorUIElement
  24200. * @param {Element} elm Element to check if it's part of the UI or not.
  24201. * @return {Boolean} True/false state if the element is part of the UI or not.
  24202. */
  24203. FocusManager.isEditorUIElement = function(elm) {
  24204. // Needs to be converted to string since svg can have focus: #6776
  24205. return elm.className.toString().indexOf('mce-') !== -1;
  24206. };
  24207. return FocusManager;
  24208. });
  24209. // Included from: js/tinymce/classes/EditorManager.js
  24210. /**
  24211. * EditorManager.js
  24212. *
  24213. * Copyright, Moxiecode Systems AB
  24214. * Released under LGPL License.
  24215. *
  24216. * License: http://www.tinymce.com/license
  24217. * Contributing: http://www.tinymce.com/contributing
  24218. */
  24219. /**
  24220. * This class used as a factory for manager for tinymce.Editor instances.
  24221. *
  24222. * @example
  24223. * tinymce.EditorManager.init({});
  24224. *
  24225. * @class tinymce.EditorManager
  24226. * @mixes tinymce.util.Observable
  24227. * @static
  24228. */
  24229. define("tinymce/EditorManager", [
  24230. "tinymce/Editor",
  24231. "tinymce/dom/DOMUtils",
  24232. "tinymce/util/URI",
  24233. "tinymce/Env",
  24234. "tinymce/util/Tools",
  24235. "tinymce/util/Observable",
  24236. "tinymce/util/I18n",
  24237. "tinymce/FocusManager"
  24238. ], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
  24239. var DOM = DOMUtils.DOM;
  24240. var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
  24241. var instanceCounter = 0, beforeUnloadDelegate, EditorManager;
  24242. function removeEditorFromList(editor) {
  24243. var editors = EditorManager.editors, removedFromList;
  24244. delete editors[editor.id];
  24245. for (var i = 0; i < editors.length; i++) {
  24246. if (editors[i] == editor) {
  24247. editors.splice(i, 1);
  24248. removedFromList = true;
  24249. break;
  24250. }
  24251. }
  24252. // Select another editor since the active one was removed
  24253. if (EditorManager.activeEditor == editor) {
  24254. EditorManager.activeEditor = editors[0];
  24255. }
  24256. // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
  24257. if (EditorManager.focusedEditor == editor) {
  24258. EditorManager.focusedEditor = null;
  24259. }
  24260. return removedFromList;
  24261. }
  24262. function purgeDestroyedEditor(editor) {
  24263. // User has manually destroyed the editor lets clean up the mess
  24264. if (editor && !(editor.getContainer() || editor.getBody()).parentNode) {
  24265. removeEditorFromList(editor);
  24266. editor.destroy(true);
  24267. editor = null;
  24268. }
  24269. return editor;
  24270. }
  24271. EditorManager = {
  24272. /**
  24273. * Major version of TinyMCE build.
  24274. *
  24275. * @property majorVersion
  24276. * @type String
  24277. */
  24278. majorVersion : '4',
  24279. /**
  24280. * Minor version of TinyMCE build.
  24281. *
  24282. * @property minorVersion
  24283. * @type String
  24284. */
  24285. minorVersion : '0.23',
  24286. /**
  24287. * Release date of TinyMCE build.
  24288. *
  24289. * @property releaseDate
  24290. * @type String
  24291. */
  24292. releaseDate: '2014-04-24',
  24293. /**
  24294. * Collection of editor instances.
  24295. *
  24296. * @property editors
  24297. * @type Object
  24298. * @example
  24299. * for (edId in tinymce.editors)
  24300. * tinymce.editors[edId].save();
  24301. */
  24302. editors: [],
  24303. /**
  24304. * Collection of language pack data.
  24305. *
  24306. * @property i18n
  24307. * @type Object
  24308. */
  24309. i18n: I18n,
  24310. /**
  24311. * Currently active editor instance.
  24312. *
  24313. * @property activeEditor
  24314. * @type tinymce.Editor
  24315. * @example
  24316. * tinyMCE.activeEditor.selection.getContent();
  24317. * tinymce.EditorManager.activeEditor.selection.getContent();
  24318. */
  24319. activeEditor: null,
  24320. setup: function() {
  24321. var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
  24322. // Get base URL for the current document
  24323. documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
  24324. if (!/[\/\\]$/.test(documentBaseURL)) {
  24325. documentBaseURL += '/';
  24326. }
  24327. // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
  24328. preInit = window.tinymce || window.tinyMCEPreInit;
  24329. if (preInit) {
  24330. baseURL = preInit.base || preInit.baseURL;
  24331. suffix = preInit.suffix;
  24332. } else {
  24333. // Get base where the tinymce script is located
  24334. var scripts = document.getElementsByTagName('script');
  24335. for (var i = 0; i < scripts.length; i++) {
  24336. src = scripts[i].src;
  24337. // Script types supported:
  24338. // tinymce.js tinymce.min.js tinymce.dev.js
  24339. // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
  24340. // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
  24341. if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
  24342. if (src.indexOf('.min') != -1) {
  24343. suffix = '.min';
  24344. }
  24345. baseURL = src.substring(0, src.lastIndexOf('/'));
  24346. break;
  24347. }
  24348. }
  24349. // We didn't find any baseURL by looking at the script elements
  24350. // Try to use the document.currentScript as a fallback
  24351. if (!baseURL && document.currentScript) {
  24352. src = document.currentScript.src;
  24353. if (src.indexOf('.min') != -1) {
  24354. suffix = '.min';
  24355. }
  24356. baseURL = src.substring(0, src.lastIndexOf('/'));
  24357. }
  24358. }
  24359. /**
  24360. * Base URL where the root directory if TinyMCE is located.
  24361. *
  24362. * @property baseURL
  24363. * @type String
  24364. */
  24365. self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
  24366. /**
  24367. * Document base URL where the current document is located.
  24368. *
  24369. * @property documentBaseURL
  24370. * @type String
  24371. */
  24372. self.documentBaseURL = documentBaseURL;
  24373. /**
  24374. * Absolute baseURI for the installation path of TinyMCE.
  24375. *
  24376. * @property baseURI
  24377. * @type tinymce.util.URI
  24378. */
  24379. self.baseURI = new URI(self.baseURL);
  24380. /**
  24381. * Current suffix to add to each plugin/theme that gets loaded for example ".min".
  24382. *
  24383. * @property suffix
  24384. * @type String
  24385. */
  24386. self.suffix = suffix;
  24387. self.focusManager = new FocusManager(self);
  24388. },
  24389. /**
  24390. * Initializes a set of editors. This method will create editors based on various settings.
  24391. *
  24392. * @method init
  24393. * @param {Object} settings Settings object to be passed to each editor instance.
  24394. * @example
  24395. * // Initializes a editor using the longer method
  24396. * tinymce.EditorManager.init({
  24397. * some_settings : 'some value'
  24398. * });
  24399. *
  24400. * // Initializes a editor instance using the shorter version
  24401. * tinyMCE.init({
  24402. * some_settings : 'some value'
  24403. * });
  24404. */
  24405. init: function(settings) {
  24406. var self = this, editors = [], editor;
  24407. function createId(elm) {
  24408. var id = elm.id;
  24409. // Use element id, or unique name or generate a unique id
  24410. if (!id) {
  24411. id = elm.name;
  24412. if (id && !DOM.get(id)) {
  24413. id = elm.name;
  24414. } else {
  24415. // Generate unique name
  24416. id = DOM.uniqueId();
  24417. }
  24418. elm.setAttribute('id', id);
  24419. }
  24420. return id;
  24421. }
  24422. function createEditor(id, settings) {
  24423. if (!purgeDestroyedEditor(self.get(id))) {
  24424. var editor = new Editor(id, settings, self);
  24425. editors.push(editor);
  24426. editor.render();
  24427. }
  24428. }
  24429. function execCallback(se, n, s) {
  24430. var f = se[n];
  24431. if (!f) {
  24432. return;
  24433. }
  24434. return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
  24435. }
  24436. function hasClass(n, c) {
  24437. return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
  24438. }
  24439. function readyHandler() {
  24440. var l, co;
  24441. DOM.unbind(window, 'ready', readyHandler);
  24442. execCallback(settings, 'onpageload');
  24443. if (settings.types) {
  24444. // Process type specific selector
  24445. each(settings.types, function(type) {
  24446. each(DOM.select(type.selector), function(elm) {
  24447. createEditor(createId(elm), extend({}, settings, type));
  24448. });
  24449. });
  24450. return;
  24451. } else if (settings.selector) {
  24452. // Process global selector
  24453. each(DOM.select(settings.selector), function(elm) {
  24454. createEditor(createId(elm), settings);
  24455. });
  24456. return;
  24457. }
  24458. // Fallback to old setting
  24459. switch (settings.mode) {
  24460. case "exact":
  24461. l = settings.elements || '';
  24462. if (l.length > 0) {
  24463. each(explode(l), function(v) {
  24464. if (DOM.get(v)) {
  24465. editor = new Editor(v, settings, self);
  24466. editors.push(editor);
  24467. editor.render();
  24468. } else {
  24469. each(document.forms, function(f) {
  24470. each(f.elements, function(e) {
  24471. if (e.name === v) {
  24472. v = 'mce_editor_' + instanceCounter++;
  24473. DOM.setAttrib(e, 'id', v);
  24474. createEditor(v, settings);
  24475. }
  24476. });
  24477. });
  24478. }
  24479. });
  24480. }
  24481. break;
  24482. case "textareas":
  24483. case "specific_textareas":
  24484. each(DOM.select('textarea'), function(elm) {
  24485. if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
  24486. return;
  24487. }
  24488. if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
  24489. createEditor(createId(elm), settings);
  24490. }
  24491. });
  24492. break;
  24493. }
  24494. // Call onInit when all editors are initialized
  24495. if (settings.oninit) {
  24496. l = co = 0;
  24497. each(editors, function(ed) {
  24498. co++;
  24499. if (!ed.initialized) {
  24500. // Wait for it
  24501. ed.on('init', function() {
  24502. l++;
  24503. // All done
  24504. if (l == co) {
  24505. execCallback(settings, 'oninit');
  24506. }
  24507. });
  24508. } else {
  24509. l++;
  24510. }
  24511. // All done
  24512. if (l == co) {
  24513. execCallback(settings, 'oninit');
  24514. }
  24515. });
  24516. }
  24517. }
  24518. self.settings = settings;
  24519. DOM.bind(window, 'ready', readyHandler);
  24520. },
  24521. /**
  24522. * Returns a editor instance by id.
  24523. *
  24524. * @method get
  24525. * @param {String/Number} id Editor instance id or index to return.
  24526. * @return {tinymce.Editor} Editor instance to return.
  24527. * @example
  24528. * // Adds an onclick event to an editor by id (shorter version)
  24529. * tinymce.get('mytextbox').on('click', function(e) {
  24530. * ed.windowManager.alert('Hello world!');
  24531. * });
  24532. *
  24533. * // Adds an onclick event to an editor by id (longer version)
  24534. * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
  24535. * ed.windowManager.alert('Hello world!');
  24536. * });
  24537. */
  24538. get: function(id) {
  24539. if (!arguments.length) {
  24540. return this.editors;
  24541. }
  24542. return id in this.editors ? this.editors[id] : null;
  24543. },
  24544. /**
  24545. * Adds an editor instance to the editor collection. This will also set it as the active editor.
  24546. *
  24547. * @method add
  24548. * @param {tinymce.Editor} editor Editor instance to add to the collection.
  24549. * @return {tinymce.Editor} The same instance that got passed in.
  24550. */
  24551. add: function(editor) {
  24552. var self = this, editors = self.editors;
  24553. // Add named and index editor instance
  24554. editors[editor.id] = editor;
  24555. editors.push(editor);
  24556. self.activeEditor = editor;
  24557. /**
  24558. * Fires when an editor is added to the EditorManager collection.
  24559. *
  24560. * @event AddEditor
  24561. * @param {Object} e Event arguments.
  24562. */
  24563. self.fire('AddEditor', {editor: editor});
  24564. if (!beforeUnloadDelegate) {
  24565. beforeUnloadDelegate = function() {
  24566. self.fire('BeforeUnload');
  24567. };
  24568. DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
  24569. }
  24570. return editor;
  24571. },
  24572. /**
  24573. * Creates an editor instance and adds it to the EditorManager collection.
  24574. *
  24575. * @method createEditor
  24576. * @param {String} id Instance id to use for editor.
  24577. * @param {Object} settings Editor instance settings.
  24578. * @return {tinymce.Editor} Editor instance that got created.
  24579. */
  24580. createEditor: function(id, settings) {
  24581. return this.add(new Editor(id, settings, this));
  24582. },
  24583. /**
  24584. * Removes a editor or editors form page.
  24585. *
  24586. * @example
  24587. * // Remove all editors bound to divs
  24588. * tinymce.remove('div');
  24589. *
  24590. * // Remove all editors bound to textareas
  24591. * tinymce.remove('textarea');
  24592. *
  24593. * // Remove all editors
  24594. * tinymce.remove();
  24595. *
  24596. * // Remove specific instance by id
  24597. * tinymce.remove('#id');
  24598. *
  24599. * @method remove
  24600. * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
  24601. * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
  24602. */
  24603. remove: function(selector) {
  24604. var self = this, i, editors = self.editors, editor;
  24605. // Remove all editors
  24606. if (!selector) {
  24607. for (i = editors.length - 1; i >= 0; i--) {
  24608. self.remove(editors[i]);
  24609. }
  24610. return;
  24611. }
  24612. // Remove editors by selector
  24613. if (typeof(selector) == "string") {
  24614. selector = selector.selector || selector;
  24615. each(DOM.select(selector), function(elm) {
  24616. self.remove(editors[elm.id]);
  24617. });
  24618. return;
  24619. }
  24620. // Remove specific editor
  24621. editor = selector;
  24622. // Not in the collection
  24623. if (!editors[editor.id]) {
  24624. return null;
  24625. }
  24626. /**
  24627. * Fires when an editor is removed from EditorManager collection.
  24628. *
  24629. * @event RemoveEditor
  24630. * @param {Object} e Event arguments.
  24631. */
  24632. if (removeEditorFromList(editor)) {
  24633. self.fire('RemoveEditor', {editor: editor});
  24634. }
  24635. if (!editors.length) {
  24636. DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
  24637. }
  24638. editor.remove();
  24639. return editor;
  24640. },
  24641. /**
  24642. * Executes a specific command on the currently active editor.
  24643. *
  24644. * @method execCommand
  24645. * @param {String} c Command to perform for example Bold.
  24646. * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
  24647. * @param {String} v Optional value parameter like for example an URL to a link.
  24648. * @return {Boolean} true/false if the command was executed or not.
  24649. */
  24650. execCommand: function(cmd, ui, value) {
  24651. var self = this, editor = self.get(value);
  24652. // Manager commands
  24653. switch (cmd) {
  24654. case "mceAddEditor":
  24655. if (!self.get(value)) {
  24656. new Editor(value, self.settings, self).render();
  24657. }
  24658. return true;
  24659. case "mceRemoveEditor":
  24660. if (editor) {
  24661. editor.remove();
  24662. }
  24663. return true;
  24664. case 'mceToggleEditor':
  24665. if (!editor) {
  24666. self.execCommand('mceAddEditor', 0, value);
  24667. return true;
  24668. }
  24669. if (editor.isHidden()) {
  24670. editor.show();
  24671. } else {
  24672. editor.hide();
  24673. }
  24674. return true;
  24675. }
  24676. // Run command on active editor
  24677. if (self.activeEditor) {
  24678. return self.activeEditor.execCommand(cmd, ui, value);
  24679. }
  24680. return false;
  24681. },
  24682. /**
  24683. * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
  24684. *
  24685. * @method triggerSave
  24686. * @example
  24687. * // Saves all contents
  24688. * tinyMCE.triggerSave();
  24689. */
  24690. triggerSave: function() {
  24691. each(this.editors, function(editor) {
  24692. editor.save();
  24693. });
  24694. },
  24695. /**
  24696. * Adds a language pack, this gets called by the loaded language files like en.js.
  24697. *
  24698. * @method addI18n
  24699. * @param {String} code Optional language code.
  24700. * @param {Object} items Name/value object with translations.
  24701. */
  24702. addI18n: function(code, items) {
  24703. I18n.add(code, items);
  24704. },
  24705. /**
  24706. * Translates the specified string using the language pack items.
  24707. *
  24708. * @method translate
  24709. * @param {String/Array/Object} text String to translate
  24710. * @return {String} Translated string.
  24711. */
  24712. translate: function(text) {
  24713. return I18n.translate(text);
  24714. }
  24715. };
  24716. extend(EditorManager, Observable);
  24717. EditorManager.setup();
  24718. // Export EditorManager as tinymce/tinymce in global namespace
  24719. window.tinymce = window.tinyMCE = EditorManager;
  24720. return EditorManager;
  24721. });
  24722. // Included from: js/tinymce/classes/LegacyInput.js
  24723. /**
  24724. * LegacyInput.js
  24725. *
  24726. * Copyright, Moxiecode Systems AB
  24727. * Released under LGPL License.
  24728. *
  24729. * License: http://www.tinymce.com/license
  24730. * Contributing: http://www.tinymce.com/contributing
  24731. */
  24732. define("tinymce/LegacyInput", [
  24733. "tinymce/EditorManager",
  24734. "tinymce/util/Tools"
  24735. ], function(EditorManager, Tools) {
  24736. var each = Tools.each, explode = Tools.explode;
  24737. EditorManager.on('AddEditor', function(e) {
  24738. var editor = e.editor;
  24739. editor.on('preInit', function() {
  24740. var filters, fontSizes, dom, settings = editor.settings;
  24741. function replaceWithSpan(node, styles) {
  24742. each(styles, function(value, name) {
  24743. if (value) {
  24744. dom.setStyle(node, name, value);
  24745. }
  24746. });
  24747. dom.rename(node, 'span');
  24748. }
  24749. function convert(e) {
  24750. dom = editor.dom;
  24751. if (settings.convert_fonts_to_spans) {
  24752. each(dom.select('font,u,strike', e.node), function(node) {
  24753. filters[node.nodeName.toLowerCase()](dom, node);
  24754. });
  24755. }
  24756. }
  24757. if (settings.inline_styles) {
  24758. fontSizes = explode(settings.font_size_legacy_values);
  24759. filters = {
  24760. font: function(dom, node) {
  24761. replaceWithSpan(node, {
  24762. backgroundColor: node.style.backgroundColor,
  24763. color: node.color,
  24764. fontFamily: node.face,
  24765. fontSize: fontSizes[parseInt(node.size, 10) - 1]
  24766. });
  24767. },
  24768. u: function(dom, node) {
  24769. replaceWithSpan(node, {
  24770. textDecoration: 'underline'
  24771. });
  24772. },
  24773. strike: function(dom, node) {
  24774. replaceWithSpan(node, {
  24775. textDecoration: 'line-through'
  24776. });
  24777. }
  24778. };
  24779. editor.on('PreProcess SetContent', convert);
  24780. }
  24781. });
  24782. });
  24783. });
  24784. // Included from: js/tinymce/classes/util/XHR.js
  24785. /**
  24786. * XHR.js
  24787. *
  24788. * Copyright, Moxiecode Systems AB
  24789. * Released under LGPL License.
  24790. *
  24791. * License: http://www.tinymce.com/license
  24792. * Contributing: http://www.tinymce.com/contributing
  24793. */
  24794. /**
  24795. * This class enables you to send XMLHTTPRequests cross browser.
  24796. * @class tinymce.util.XHR
  24797. * @static
  24798. * @example
  24799. * // Sends a low level Ajax request
  24800. * tinymce.util.XHR.send({
  24801. * url: 'someurl',
  24802. * success: function(text) {
  24803. * console.debug(text);
  24804. * }
  24805. * });
  24806. */
  24807. define("tinymce/util/XHR", [], function() {
  24808. return {
  24809. /**
  24810. * Sends a XMLHTTPRequest.
  24811. * Consult the Wiki for details on what settings this method takes.
  24812. *
  24813. * @method send
  24814. * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
  24815. */
  24816. send: function(settings) {
  24817. var xhr, count = 0;
  24818. function ready() {
  24819. if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
  24820. if (settings.success && count < 10000 && xhr.status == 200) {
  24821. settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
  24822. } else if (settings.error) {
  24823. settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
  24824. }
  24825. xhr = null;
  24826. } else {
  24827. setTimeout(ready, 10);
  24828. }
  24829. }
  24830. // Default settings
  24831. settings.scope = settings.scope || this;
  24832. settings.success_scope = settings.success_scope || settings.scope;
  24833. settings.error_scope = settings.error_scope || settings.scope;
  24834. settings.async = settings.async === false ? false : true;
  24835. settings.data = settings.data || '';
  24836. xhr = new XMLHttpRequest();
  24837. if (xhr) {
  24838. if (xhr.overrideMimeType) {
  24839. xhr.overrideMimeType(settings.content_type);
  24840. }
  24841. xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
  24842. if (settings.content_type) {
  24843. xhr.setRequestHeader('Content-Type', settings.content_type);
  24844. }
  24845. xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  24846. xhr.send(settings.data);
  24847. // Syncronous request
  24848. if (!settings.async) {
  24849. return ready();
  24850. }
  24851. // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
  24852. setTimeout(ready, 10);
  24853. }
  24854. }
  24855. };
  24856. });
  24857. // Included from: js/tinymce/classes/util/JSON.js
  24858. /**
  24859. * JSON.js
  24860. *
  24861. * Copyright, Moxiecode Systems AB
  24862. * Released under LGPL License.
  24863. *
  24864. * License: http://www.tinymce.com/license
  24865. * Contributing: http://www.tinymce.com/contributing
  24866. */
  24867. /**
  24868. * JSON parser and serializer class.
  24869. *
  24870. * @class tinymce.util.JSON
  24871. * @static
  24872. * @example
  24873. * // JSON parse a string into an object
  24874. * var obj = tinymce.util.JSON.parse(somestring);
  24875. *
  24876. * // JSON serialize a object into an string
  24877. * var str = tinymce.util.JSON.serialize(obj);
  24878. */
  24879. define("tinymce/util/JSON", [], function() {
  24880. function serialize(o, quote) {
  24881. var i, v, t, name;
  24882. quote = quote || '"';
  24883. if (o === null) {
  24884. return 'null';
  24885. }
  24886. t = typeof o;
  24887. if (t == 'string') {
  24888. v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
  24889. return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
  24890. // Make sure single quotes never get encoded inside double quotes for JSON compatibility
  24891. if (quote === '"' && a === "'") {
  24892. return a;
  24893. }
  24894. i = v.indexOf(b);
  24895. if (i + 1) {
  24896. return '\\' + v.charAt(i + 1);
  24897. }
  24898. a = b.charCodeAt().toString(16);
  24899. return '\\u' + '0000'.substring(a.length) + a;
  24900. }) + quote;
  24901. }
  24902. if (t == 'object') {
  24903. if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
  24904. for (i = 0, v = '['; i < o.length; i++) {
  24905. v += (i > 0 ? ',' : '') + serialize(o[i], quote);
  24906. }
  24907. return v + ']';
  24908. }
  24909. v = '{';
  24910. for (name in o) {
  24911. if (o.hasOwnProperty(name)) {
  24912. v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
  24913. quote + ':' + serialize(o[name], quote) : '';
  24914. }
  24915. }
  24916. return v + '}';
  24917. }
  24918. return '' + o;
  24919. }
  24920. return {
  24921. /**
  24922. * Serializes the specified object as a JSON string.
  24923. *
  24924. * @method serialize
  24925. * @param {Object} obj Object to serialize as a JSON string.
  24926. * @param {String} quote Optional quote string defaults to ".
  24927. * @return {string} JSON string serialized from input.
  24928. */
  24929. serialize: serialize,
  24930. /**
  24931. * Unserializes/parses the specified JSON string into a object.
  24932. *
  24933. * @method parse
  24934. * @param {string} s JSON String to parse into a JavaScript object.
  24935. * @return {Object} Object from input JSON string or undefined if it failed.
  24936. */
  24937. parse: function(text) {
  24938. try {
  24939. // Trick uglify JS
  24940. return window[String.fromCharCode(101) + 'val']('(' + text + ')');
  24941. } catch (ex) {
  24942. // Ignore
  24943. }
  24944. }
  24945. /**#@-*/
  24946. };
  24947. });
  24948. // Included from: js/tinymce/classes/util/JSONRequest.js
  24949. /**
  24950. * JSONRequest.js
  24951. *
  24952. * Copyright, Moxiecode Systems AB
  24953. * Released under LGPL License.
  24954. *
  24955. * License: http://www.tinymce.com/license
  24956. * Contributing: http://www.tinymce.com/contributing
  24957. */
  24958. /**
  24959. * This class enables you to use JSON-RPC to call backend methods.
  24960. *
  24961. * @class tinymce.util.JSONRequest
  24962. * @example
  24963. * var json = new tinymce.util.JSONRequest({
  24964. * url: 'somebackend.php'
  24965. * });
  24966. *
  24967. * // Send RPC call 1
  24968. * json.send({
  24969. * method: 'someMethod1',
  24970. * params: ['a', 'b'],
  24971. * success: function(result) {
  24972. * console.dir(result);
  24973. * }
  24974. * });
  24975. *
  24976. * // Send RPC call 2
  24977. * json.send({
  24978. * method: 'someMethod2',
  24979. * params: ['a', 'b'],
  24980. * success: function(result) {
  24981. * console.dir(result);
  24982. * }
  24983. * });
  24984. */
  24985. define("tinymce/util/JSONRequest", [
  24986. "tinymce/util/JSON",
  24987. "tinymce/util/XHR",
  24988. "tinymce/util/Tools"
  24989. ], function(JSON, XHR, Tools) {
  24990. var extend = Tools.extend;
  24991. function JSONRequest(settings) {
  24992. this.settings = extend({}, settings);
  24993. this.count = 0;
  24994. }
  24995. /**
  24996. * Simple helper function to send a JSON-RPC request without the need to initialize an object.
  24997. * Consult the Wiki API documentation for more details on what you can pass to this function.
  24998. *
  24999. * @method sendRPC
  25000. * @static
  25001. * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
  25002. */
  25003. JSONRequest.sendRPC = function(o) {
  25004. return new JSONRequest().send(o);
  25005. };
  25006. JSONRequest.prototype = {
  25007. /**
  25008. * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
  25009. *
  25010. * @method send
  25011. * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
  25012. */
  25013. send: function(args) {
  25014. var ecb = args.error, scb = args.success;
  25015. args = extend(this.settings, args);
  25016. args.success = function(c, x) {
  25017. c = JSON.parse(c);
  25018. if (typeof(c) == 'undefined') {
  25019. c = {
  25020. error : 'JSON Parse error.'
  25021. };
  25022. }
  25023. if (c.error) {
  25024. ecb.call(args.error_scope || args.scope, c.error, x);
  25025. } else {
  25026. scb.call(args.success_scope || args.scope, c.result);
  25027. }
  25028. };
  25029. args.error = function(ty, x) {
  25030. if (ecb) {
  25031. ecb.call(args.error_scope || args.scope, ty, x);
  25032. }
  25033. };
  25034. args.data = JSON.serialize({
  25035. id: args.id || 'c' + (this.count++),
  25036. method: args.method,
  25037. params: args.params
  25038. });
  25039. // JSON content type for Ruby on rails. Bug: #1883287
  25040. args.content_type = 'application/json';
  25041. XHR.send(args);
  25042. }
  25043. };
  25044. return JSONRequest;
  25045. });
  25046. // Included from: js/tinymce/classes/util/JSONP.js
  25047. /**
  25048. * JSONP.js
  25049. *
  25050. * Copyright, Moxiecode Systems AB
  25051. * Released under LGPL License.
  25052. *
  25053. * License: http://www.tinymce.com/license
  25054. * Contributing: http://www.tinymce.com/contributing
  25055. */
  25056. define("tinymce/util/JSONP", [
  25057. "tinymce/dom/DOMUtils"
  25058. ], function(DOMUtils) {
  25059. return {
  25060. callbacks: {},
  25061. count: 0,
  25062. send: function(settings) {
  25063. var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
  25064. var id = 'tinymce_jsonp_' + count;
  25065. self.callbacks[count] = function(json) {
  25066. dom.remove(id);
  25067. delete self.callbacks[count];
  25068. settings.callback(json);
  25069. };
  25070. dom.add(dom.doc.body, 'script', {
  25071. id: id,
  25072. src: settings.url,
  25073. type: 'text/javascript'
  25074. });
  25075. self.count++;
  25076. }
  25077. };
  25078. });
  25079. // Included from: js/tinymce/classes/util/LocalStorage.js
  25080. /**
  25081. * LocalStorage.js
  25082. *
  25083. * Copyright, Moxiecode Systems AB
  25084. * Released under LGPL License.
  25085. *
  25086. * License: http://www.tinymce.com/license
  25087. * Contributing: http://www.tinymce.com/contributing
  25088. */
  25089. /**
  25090. * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
  25091. * Storage is done using userData on IE 7 and a special serialization format. The format is designed
  25092. * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
  25093. * makes it possible to store for example HTML data.
  25094. *
  25095. * Storage format for userData:
  25096. * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
  25097. *
  25098. * For example this data key1=value1,key2=value2 would be:
  25099. * 4,key1,6,value1,4,key2,6,value2
  25100. *
  25101. * @class tinymce.util.LocalStorage
  25102. * @static
  25103. * @version 4.0
  25104. * @example
  25105. * tinymce.util.LocalStorage.setItem('key', 'value');
  25106. * var value = tinymce.util.LocalStorage.getItem('key');
  25107. */
  25108. define("tinymce/util/LocalStorage", [], function() {
  25109. var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
  25110. // Check for native support
  25111. try {
  25112. if (window.localStorage) {
  25113. return localStorage;
  25114. }
  25115. } catch (ex) {
  25116. // Ignore
  25117. }
  25118. userDataKey = "tinymce";
  25119. storageElm = document.documentElement;
  25120. hasOldIEDataSupport = !!storageElm.addBehavior;
  25121. if (hasOldIEDataSupport) {
  25122. storageElm.addBehavior('#default#userData');
  25123. }
  25124. /**
  25125. * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
  25126. */
  25127. function updateKeys() {
  25128. keys = [];
  25129. for (var key in items) {
  25130. keys.push(key);
  25131. }
  25132. LocalStorage.length = keys.length;
  25133. }
  25134. /**
  25135. * Loads the userData string and parses it into the items structure.
  25136. */
  25137. function load() {
  25138. var key, data, value, pos = 0;
  25139. items = {};
  25140. // localStorage can be disabled on WebKit/Gecko so make a dummy storage
  25141. if (!hasOldIEDataSupport) {
  25142. return;
  25143. }
  25144. function next(end) {
  25145. var value, nextPos;
  25146. nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
  25147. if (nextPos === -1 || nextPos > data.length) {
  25148. return null;
  25149. }
  25150. value = data.substring(pos, nextPos);
  25151. pos = nextPos + 1;
  25152. return value;
  25153. }
  25154. storageElm.load(userDataKey);
  25155. data = storageElm.getAttribute(userDataKey) || '';
  25156. do {
  25157. var offset = next();
  25158. if (offset === null) {
  25159. break;
  25160. }
  25161. key = next(parseInt(offset, 32) || 0);
  25162. if (key !== null) {
  25163. offset = next();
  25164. if (offset === null) {
  25165. break;
  25166. }
  25167. value = next(parseInt(offset, 32) || 0);
  25168. if (key) {
  25169. items[key] = value;
  25170. }
  25171. }
  25172. } while (key !== null);
  25173. updateKeys();
  25174. }
  25175. /**
  25176. * Saves the items structure into a the userData format.
  25177. */
  25178. function save() {
  25179. var value, data = '';
  25180. // localStorage can be disabled on WebKit/Gecko so make a dummy storage
  25181. if (!hasOldIEDataSupport) {
  25182. return;
  25183. }
  25184. for (var key in items) {
  25185. value = items[key];
  25186. data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
  25187. }
  25188. storageElm.setAttribute(userDataKey, data);
  25189. try {
  25190. storageElm.save(userDataKey);
  25191. } catch (ex) {
  25192. // Ignore disk full
  25193. }
  25194. updateKeys();
  25195. }
  25196. LocalStorage = {
  25197. /**
  25198. * Length of the number of items in storage.
  25199. *
  25200. * @property length
  25201. * @type Number
  25202. * @return {Number} Number of items in storage.
  25203. */
  25204. //length:0,
  25205. /**
  25206. * Returns the key name by index.
  25207. *
  25208. * @method key
  25209. * @param {Number} index Index of key to return.
  25210. * @return {String} Key value or null if it wasn't found.
  25211. */
  25212. key: function(index) {
  25213. return keys[index];
  25214. },
  25215. /**
  25216. * Returns the value if the specified key or null if it wasn't found.
  25217. *
  25218. * @method getItem
  25219. * @param {String} key Key of item to retrive.
  25220. * @return {String} Value of the specified item or null if it wasn't found.
  25221. */
  25222. getItem: function(key) {
  25223. return key in items ? items[key] : null;
  25224. },
  25225. /**
  25226. * Sets the value of the specified item by it's key.
  25227. *
  25228. * @method setItem
  25229. * @param {String} key Key of the item to set.
  25230. * @param {String} value Value of the item to set.
  25231. */
  25232. setItem: function(key, value) {
  25233. items[key] = "" + value;
  25234. save();
  25235. },
  25236. /**
  25237. * Removes the specified item by key.
  25238. *
  25239. * @method removeItem
  25240. * @param {String} key Key of item to remove.
  25241. */
  25242. removeItem: function(key) {
  25243. delete items[key];
  25244. save();
  25245. },
  25246. /**
  25247. * Removes all items.
  25248. *
  25249. * @method clear
  25250. */
  25251. clear: function() {
  25252. items = {};
  25253. save();
  25254. }
  25255. };
  25256. load();
  25257. return LocalStorage;
  25258. });
  25259. // Included from: js/tinymce/classes/Compat.js
  25260. /**
  25261. * Compat.js
  25262. *
  25263. * Copyright, Moxiecode Systems AB
  25264. * Released under LGPL License.
  25265. *
  25266. * License: http://www.tinymce.com/license
  25267. * Contributing: http://www.tinymce.com/contributing
  25268. */
  25269. /**
  25270. * TinyMCE core class.
  25271. *
  25272. * @static
  25273. * @class tinymce
  25274. * @borrow-members tinymce.EditorManager
  25275. * @borrow-members tinymce.util.Tools
  25276. */
  25277. define("tinymce/Compat", [
  25278. "tinymce/dom/DOMUtils",
  25279. "tinymce/dom/EventUtils",
  25280. "tinymce/dom/ScriptLoader",
  25281. "tinymce/AddOnManager",
  25282. "tinymce/util/Tools",
  25283. "tinymce/Env"
  25284. ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
  25285. var tinymce = window.tinymce;
  25286. /**
  25287. * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
  25288. * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
  25289. * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
  25290. * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
  25291. */
  25292. tinymce.DOM = DOMUtils.DOM;
  25293. tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
  25294. tinymce.PluginManager = AddOnManager.PluginManager;
  25295. tinymce.ThemeManager = AddOnManager.ThemeManager;
  25296. tinymce.dom = tinymce.dom || {};
  25297. tinymce.dom.Event = EventUtils.Event;
  25298. Tools.each(Tools, function(func, key) {
  25299. tinymce[key] = func;
  25300. });
  25301. Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
  25302. tinymce[name] = Env[name.substr(2).toLowerCase()];
  25303. });
  25304. return {};
  25305. });
  25306. // Describe the different namespaces
  25307. /**
  25308. * Root level namespace this contains classes directly releated to the TinyMCE editor.
  25309. *
  25310. * @namespace tinymce
  25311. */
  25312. /**
  25313. * Contains classes for handling the browsers DOM.
  25314. *
  25315. * @namespace tinymce.dom
  25316. */
  25317. /**
  25318. * Contains html parser and serializer logic.
  25319. *
  25320. * @namespace tinymce.html
  25321. */
  25322. /**
  25323. * Contains the different UI types such as buttons, listboxes etc.
  25324. *
  25325. * @namespace tinymce.ui
  25326. */
  25327. /**
  25328. * Contains various utility classes such as json parser, cookies etc.
  25329. *
  25330. * @namespace tinymce.util
  25331. */
  25332. // Included from: js/tinymce/classes/ui/Layout.js
  25333. /**
  25334. * Layout.js
  25335. *
  25336. * Copyright, Moxiecode Systems AB
  25337. * Released under LGPL License.
  25338. *
  25339. * License: http://www.tinymce.com/license
  25340. * Contributing: http://www.tinymce.com/contributing
  25341. */
  25342. /**
  25343. * Base layout manager class.
  25344. *
  25345. * @class tinymce.ui.Layout
  25346. */
  25347. define("tinymce/ui/Layout", [
  25348. "tinymce/util/Class",
  25349. "tinymce/util/Tools"
  25350. ], function(Class, Tools) {
  25351. "use strict";
  25352. return Class.extend({
  25353. Defaults: {
  25354. firstControlClass: 'first',
  25355. lastControlClass: 'last'
  25356. },
  25357. /**
  25358. * Constructs a layout instance with the specified settings.
  25359. *
  25360. * @constructor
  25361. * @param {Object} settings Name/value object with settings.
  25362. */
  25363. init: function(settings) {
  25364. this.settings = Tools.extend({}, this.Defaults, settings);
  25365. },
  25366. /**
  25367. * This method gets invoked before the layout renders the controls.
  25368. *
  25369. * @method preRender
  25370. * @param {tinymce.ui.Container} container Container instance to preRender.
  25371. */
  25372. preRender: function(container) {
  25373. container.addClass(this.settings.containerClass, 'body');
  25374. },
  25375. /**
  25376. * Applies layout classes to the container.
  25377. *
  25378. * @private
  25379. */
  25380. applyClasses: function(container) {
  25381. var self = this, settings = self.settings, items, firstClass, lastClass;
  25382. items = container.items().filter(':visible');
  25383. firstClass = settings.firstControlClass;
  25384. lastClass = settings.lastControlClass;
  25385. items.each(function(item) {
  25386. item.removeClass(firstClass).removeClass(lastClass);
  25387. if (settings.controlClass) {
  25388. item.addClass(settings.controlClass);
  25389. }
  25390. });
  25391. items.eq(0).addClass(firstClass);
  25392. items.eq(-1).addClass(lastClass);
  25393. },
  25394. /**
  25395. * Renders the specified container and any layout specific HTML.
  25396. *
  25397. * @method renderHtml
  25398. * @param {tinymce.ui.Container} container Container to render HTML for.
  25399. */
  25400. renderHtml: function(container) {
  25401. var self = this, settings = self.settings, items, html = '';
  25402. items = container.items();
  25403. items.eq(0).addClass(settings.firstControlClass);
  25404. items.eq(-1).addClass(settings.lastControlClass);
  25405. items.each(function(item) {
  25406. if (settings.controlClass) {
  25407. item.addClass(settings.controlClass);
  25408. }
  25409. html += item.renderHtml();
  25410. });
  25411. return html;
  25412. },
  25413. /**
  25414. * Recalculates the positions of the controls in the specified container.
  25415. *
  25416. * @method recalc
  25417. * @param {tinymce.ui.Container} container Container instance to recalc.
  25418. */
  25419. recalc: function() {
  25420. },
  25421. /**
  25422. * This method gets invoked after the layout renders the controls.
  25423. *
  25424. * @method postRender
  25425. * @param {tinymce.ui.Container} container Container instance to postRender.
  25426. */
  25427. postRender: function() {
  25428. }
  25429. });
  25430. });
  25431. // Included from: js/tinymce/classes/ui/AbsoluteLayout.js
  25432. /**
  25433. * AbsoluteLayout.js
  25434. *
  25435. * Copyright, Moxiecode Systems AB
  25436. * Released under LGPL License.
  25437. *
  25438. * License: http://www.tinymce.com/license
  25439. * Contributing: http://www.tinymce.com/contributing
  25440. */
  25441. /**
  25442. * LayoutManager for absolute positioning. This layout manager is more of
  25443. * a base class for other layouts but can be created and used directly.
  25444. *
  25445. * @-x-less AbsoluteLayout.less
  25446. * @class tinymce.ui.AbsoluteLayout
  25447. * @extends tinymce.ui.Layout
  25448. */
  25449. define("tinymce/ui/AbsoluteLayout", [
  25450. "tinymce/ui/Layout"
  25451. ], function(Layout) {
  25452. "use strict";
  25453. return Layout.extend({
  25454. Defaults: {
  25455. containerClass: 'abs-layout',
  25456. controlClass: 'abs-layout-item'
  25457. },
  25458. /**
  25459. * Recalculates the positions of the controls in the specified container.
  25460. *
  25461. * @method recalc
  25462. * @param {tinymce.ui.Container} container Container instance to recalc.
  25463. */
  25464. recalc: function(container) {
  25465. container.items().filter(':visible').each(function(ctrl) {
  25466. var settings = ctrl.settings;
  25467. ctrl.layoutRect({
  25468. x: settings.x,
  25469. y: settings.y,
  25470. w: settings.w,
  25471. h: settings.h
  25472. });
  25473. if (ctrl.recalc) {
  25474. ctrl.recalc();
  25475. }
  25476. });
  25477. },
  25478. /**
  25479. * Renders the specified container and any layout specific HTML.
  25480. *
  25481. * @method renderHtml
  25482. * @param {tinymce.ui.Container} container Container to render HTML for.
  25483. */
  25484. renderHtml: function(container) {
  25485. return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
  25486. }
  25487. });
  25488. });
  25489. // Included from: js/tinymce/classes/ui/Tooltip.js
  25490. /**
  25491. * Tooltip.js
  25492. *
  25493. * Copyright, Moxiecode Systems AB
  25494. * Released under LGPL License.
  25495. *
  25496. * License: http://www.tinymce.com/license
  25497. * Contributing: http://www.tinymce.com/contributing
  25498. */
  25499. /**
  25500. * Creates a tooltip instance.
  25501. *
  25502. * @-x-less ToolTip.less
  25503. * @class tinymce.ui.ToolTip
  25504. * @extends tinymce.ui.Control
  25505. * @mixes tinymce.ui.Movable
  25506. */
  25507. define("tinymce/ui/Tooltip", [
  25508. "tinymce/ui/Control",
  25509. "tinymce/ui/Movable"
  25510. ], function(Control, Movable) {
  25511. return Control.extend({
  25512. Mixins: [Movable],
  25513. Defaults: {
  25514. classes: 'widget tooltip tooltip-n'
  25515. },
  25516. /**
  25517. * Sets/gets the current label text.
  25518. *
  25519. * @method text
  25520. * @param {String} [text] New label text.
  25521. * @return {String|tinymce.ui.Tooltip} Current text or current label instance.
  25522. */
  25523. text: function(value) {
  25524. var self = this;
  25525. if (typeof(value) != "undefined") {
  25526. self._value = value;
  25527. if (self._rendered) {
  25528. self.getEl().lastChild.innerHTML = self.encode(value);
  25529. }
  25530. return self;
  25531. }
  25532. return self._value;
  25533. },
  25534. /**
  25535. * Renders the control as a HTML string.
  25536. *
  25537. * @method renderHtml
  25538. * @return {String} HTML representing the control.
  25539. */
  25540. renderHtml: function() {
  25541. var self = this, prefix = self.classPrefix;
  25542. return (
  25543. '<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
  25544. '<div class="' + prefix + 'tooltip-arrow"></div>' +
  25545. '<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
  25546. '</div>'
  25547. );
  25548. },
  25549. /**
  25550. * Repaints the control after a layout operation.
  25551. *
  25552. * @method repaint
  25553. */
  25554. repaint: function() {
  25555. var self = this, style, rect;
  25556. style = self.getEl().style;
  25557. rect = self._layoutRect;
  25558. style.left = rect.x + 'px';
  25559. style.top = rect.y + 'px';
  25560. style.zIndex = 0xFFFF + 0xFFFF;
  25561. }
  25562. });
  25563. });
  25564. // Included from: js/tinymce/classes/ui/Widget.js
  25565. /**
  25566. * Widget.js
  25567. *
  25568. * Copyright, Moxiecode Systems AB
  25569. * Released under LGPL License.
  25570. *
  25571. * License: http://www.tinymce.com/license
  25572. * Contributing: http://www.tinymce.com/contributing
  25573. */
  25574. /**
  25575. * Widget base class a widget is a control that has a tooltip and some basic states.
  25576. *
  25577. * @class tinymce.ui.Widget
  25578. * @extends tinymce.ui.Control
  25579. */
  25580. define("tinymce/ui/Widget", [
  25581. "tinymce/ui/Control",
  25582. "tinymce/ui/Tooltip"
  25583. ], function(Control, Tooltip) {
  25584. "use strict";
  25585. var tooltip;
  25586. var Widget = Control.extend({
  25587. /**
  25588. * Constructs a instance with the specified settings.
  25589. *
  25590. * @constructor
  25591. * @param {Object} settings Name/value object with settings.
  25592. * @setting {String} tooltip Tooltip text to display when hovering.
  25593. * @setting {Boolean} autofocus True if the control should be focused when rendered.
  25594. * @setting {String} text Text to display inside widget.
  25595. */
  25596. init: function(settings) {
  25597. var self = this;
  25598. self._super(settings);
  25599. settings = self.settings;
  25600. self.canFocus = true;
  25601. if (settings.tooltip && Widget.tooltips !== false) {
  25602. self.on('mouseenter', function(e) {
  25603. var tooltip = self.tooltip().moveTo(-0xFFFF);
  25604. if (e.control == self) {
  25605. var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
  25606. tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
  25607. tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
  25608. tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');
  25609. tooltip.moveRel(self.getEl(), rel);
  25610. } else {
  25611. tooltip.hide();
  25612. }
  25613. });
  25614. self.on('mouseleave mousedown click', function() {
  25615. self.tooltip().hide();
  25616. });
  25617. }
  25618. self.aria('label', settings.ariaLabel || settings.tooltip);
  25619. },
  25620. /**
  25621. * Returns the current tooltip instance.
  25622. *
  25623. * @method tooltip
  25624. * @return {tinymce.ui.Tooltip} Tooltip instance.
  25625. */
  25626. tooltip: function() {
  25627. if (!tooltip) {
  25628. tooltip = new Tooltip({type: 'tooltip'});
  25629. tooltip.renderTo();
  25630. }
  25631. return tooltip;
  25632. },
  25633. /**
  25634. * Sets/gets the active state of the widget.
  25635. *
  25636. * @method active
  25637. * @param {Boolean} [state] State if the control is active.
  25638. * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
  25639. */
  25640. active: function(state) {
  25641. var self = this, undef;
  25642. if (state !== undef) {
  25643. self.aria('pressed', state);
  25644. self.toggleClass('active', state);
  25645. }
  25646. return self._super(state);
  25647. },
  25648. /**
  25649. * Sets/gets the disabled state of the widget.
  25650. *
  25651. * @method disabled
  25652. * @param {Boolean} [state] State if the control is disabled.
  25653. * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
  25654. */
  25655. disabled: function(state) {
  25656. var self = this, undef;
  25657. if (state !== undef) {
  25658. self.aria('disabled', state);
  25659. self.toggleClass('disabled', state);
  25660. }
  25661. return self._super(state);
  25662. },
  25663. /**
  25664. * Called after the control has been rendered.
  25665. *
  25666. * @method postRender
  25667. */
  25668. postRender: function() {
  25669. var self = this, settings = self.settings;
  25670. self._rendered = true;
  25671. self._super();
  25672. if (!self.parent() && (settings.width || settings.height)) {
  25673. self.initLayoutRect();
  25674. self.repaint();
  25675. }
  25676. if (settings.autofocus) {
  25677. self.focus();
  25678. }
  25679. },
  25680. /**
  25681. * Removes the current control from DOM and from UI collections.
  25682. *
  25683. * @method remove
  25684. * @return {tinymce.ui.Control} Current control instance.
  25685. */
  25686. remove: function() {
  25687. this._super();
  25688. if (tooltip) {
  25689. tooltip.remove();
  25690. tooltip = null;
  25691. }
  25692. }
  25693. });
  25694. return Widget;
  25695. });
  25696. // Included from: js/tinymce/classes/ui/Button.js
  25697. /**
  25698. * Button.js
  25699. *
  25700. * Copyright, Moxiecode Systems AB
  25701. * Released under LGPL License.
  25702. *
  25703. * License: http://www.tinymce.com/license
  25704. * Contributing: http://www.tinymce.com/contributing
  25705. */
  25706. /**
  25707. * This class is used to create buttons. You can create them directly or through the Factory.
  25708. *
  25709. * @example
  25710. * // Create and render a button to the body element
  25711. * tinymce.ui.Factory.create({
  25712. * type: 'button',
  25713. * text: 'My button'
  25714. * }).renderTo(document.body);
  25715. *
  25716. * @-x-less Button.less
  25717. * @class tinymce.ui.Button
  25718. * @extends tinymce.ui.Widget
  25719. */
  25720. define("tinymce/ui/Button", [
  25721. "tinymce/ui/Widget"
  25722. ], function(Widget) {
  25723. "use strict";
  25724. return Widget.extend({
  25725. Defaults: {
  25726. classes: "widget btn",
  25727. role: "button"
  25728. },
  25729. /**
  25730. * Constructs a new button instance with the specified settings.
  25731. *
  25732. * @constructor
  25733. * @param {Object} settings Name/value object with settings.
  25734. * @setting {String} size Size of the button small|medium|large.
  25735. * @setting {String} image Image to use for icon.
  25736. * @setting {String} icon Icon to use for button.
  25737. */
  25738. init: function(settings) {
  25739. var self = this, size;
  25740. self.on('click mousedown', function(e) {
  25741. e.preventDefault();
  25742. });
  25743. self._super(settings);
  25744. size = settings.size;
  25745. if (settings.subtype) {
  25746. self.addClass(settings.subtype);
  25747. }
  25748. if (size) {
  25749. self.addClass('btn-' + size);
  25750. }
  25751. },
  25752. /**
  25753. * Sets/gets the current button icon.
  25754. *
  25755. * @method icon
  25756. * @param {String} [icon] New icon identifier.
  25757. * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
  25758. */
  25759. icon: function(icon) {
  25760. var self = this, prefix = self.classPrefix;
  25761. if (typeof(icon) == 'undefined') {
  25762. return self.settings.icon;
  25763. }
  25764. self.settings.icon = icon;
  25765. icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  25766. if (self._rendered) {
  25767. var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
  25768. if (icon) {
  25769. if (!iconElm || iconElm != btnElm.firstChild) {
  25770. iconElm = document.createElement('i');
  25771. btnElm.insertBefore(iconElm, btnElm.firstChild);
  25772. }
  25773. iconElm.className = icon;
  25774. } else if (iconElm) {
  25775. btnElm.removeChild(iconElm);
  25776. }
  25777. self.text(self._text); // Set text again to fix whitespace between icon + text
  25778. }
  25779. return self;
  25780. },
  25781. /**
  25782. * Repaints the button for example after it's been resizes by a layout engine.
  25783. *
  25784. * @method repaint
  25785. */
  25786. repaint: function() {
  25787. var btnStyle = this.getEl().firstChild.style;
  25788. btnStyle.width = btnStyle.height = "100%";
  25789. this._super();
  25790. },
  25791. /**
  25792. * Sets/gets the current button text.
  25793. *
  25794. * @method text
  25795. * @param {String} [text] New button text.
  25796. * @return {String|tinymce.ui.Button} Current text or current Button instance.
  25797. */
  25798. text: function(text) {
  25799. var self = this;
  25800. if (self._rendered) {
  25801. var textNode = self.getEl().lastChild.lastChild;
  25802. if (textNode) {
  25803. textNode.data = self.translate(text);
  25804. }
  25805. }
  25806. return self._super(text);
  25807. },
  25808. /**
  25809. * Renders the control as a HTML string.
  25810. *
  25811. * @method renderHtml
  25812. * @return {String} HTML representing the control.
  25813. */
  25814. renderHtml: function() {
  25815. var self = this, id = self._id, prefix = self.classPrefix;
  25816. var icon = self.settings.icon, image;
  25817. image = self.settings.image;
  25818. if (image) {
  25819. icon = 'none';
  25820. // Support for [high dpi, low dpi] image sources
  25821. if (typeof image != "string") {
  25822. image = window.getSelection ? image[0] : image[1];
  25823. }
  25824. image = ' style="background-image: url(\'' + image + '\')"';
  25825. } else {
  25826. image = '';
  25827. }
  25828. icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  25829. return (
  25830. '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
  25831. '<button role="presentation" type="button" tabindex="-1">' +
  25832. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  25833. (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
  25834. '</button>' +
  25835. '</div>'
  25836. );
  25837. }
  25838. });
  25839. });
  25840. // Included from: js/tinymce/classes/ui/ButtonGroup.js
  25841. /**
  25842. * ButtonGroup.js
  25843. *
  25844. * Copyright, Moxiecode Systems AB
  25845. * Released under LGPL License.
  25846. *
  25847. * License: http://www.tinymce.com/license
  25848. * Contributing: http://www.tinymce.com/contributing
  25849. */
  25850. /**
  25851. * This control enables you to put multiple buttons into a group. This is
  25852. * useful when you want to combine similar toolbar buttons into a group.
  25853. *
  25854. * @example
  25855. * // Create and render a buttongroup with two buttons to the body element
  25856. * tinymce.ui.Factory.create({
  25857. * type: 'buttongroup',
  25858. * items: [
  25859. * {text: 'Button A'},
  25860. * {text: 'Button B'}
  25861. * ]
  25862. * }).renderTo(document.body);
  25863. *
  25864. * @-x-less ButtonGroup.less
  25865. * @class tinymce.ui.ButtonGroup
  25866. * @extends tinymce.ui.Container
  25867. */
  25868. define("tinymce/ui/ButtonGroup", [
  25869. "tinymce/ui/Container"
  25870. ], function(Container) {
  25871. "use strict";
  25872. return Container.extend({
  25873. Defaults: {
  25874. defaultType: 'button',
  25875. role: 'group'
  25876. },
  25877. /**
  25878. * Renders the control as a HTML string.
  25879. *
  25880. * @method renderHtml
  25881. * @return {String} HTML representing the control.
  25882. */
  25883. renderHtml: function() {
  25884. var self = this, layout = self._layout;
  25885. self.addClass('btn-group');
  25886. self.preRender();
  25887. layout.preRender(self);
  25888. return (
  25889. '<div id="' + self._id + '" class="' + self.classes() + '">' +
  25890. '<div id="' + self._id + '-body">' +
  25891. (self.settings.html || '') + layout.renderHtml(self) +
  25892. '</div>' +
  25893. '</div>'
  25894. );
  25895. }
  25896. });
  25897. });
  25898. // Included from: js/tinymce/classes/ui/Checkbox.js
  25899. /**
  25900. * Checkbox.js
  25901. *
  25902. * Copyright, Moxiecode Systems AB
  25903. * Released under LGPL License.
  25904. *
  25905. * License: http://www.tinymce.com/license
  25906. * Contributing: http://www.tinymce.com/contributing
  25907. */
  25908. /**
  25909. * This control creates a custom checkbox.
  25910. *
  25911. * @example
  25912. * // Create and render a checkbox to the body element
  25913. * tinymce.ui.Factory.create({
  25914. * type: 'checkbox',
  25915. * checked: true,
  25916. * text: 'My checkbox'
  25917. * }).renderTo(document.body);
  25918. *
  25919. * @-x-less Checkbox.less
  25920. * @class tinymce.ui.Checkbox
  25921. * @extends tinymce.ui.Widget
  25922. */
  25923. define("tinymce/ui/Checkbox", [
  25924. "tinymce/ui/Widget"
  25925. ], function(Widget) {
  25926. "use strict";
  25927. return Widget.extend({
  25928. Defaults: {
  25929. classes: "checkbox",
  25930. role: "checkbox",
  25931. checked: false
  25932. },
  25933. /**
  25934. * Constructs a new Checkbox instance with the specified settings.
  25935. *
  25936. * @constructor
  25937. * @param {Object} settings Name/value object with settings.
  25938. * @setting {Boolean} checked True if the checkbox should be checked by default.
  25939. */
  25940. init: function(settings) {
  25941. var self = this;
  25942. self._super(settings);
  25943. self.on('click mousedown', function(e) {
  25944. e.preventDefault();
  25945. });
  25946. self.on('click', function(e) {
  25947. e.preventDefault();
  25948. if (!self.disabled()) {
  25949. self.checked(!self.checked());
  25950. }
  25951. });
  25952. self.checked(self.settings.checked);
  25953. },
  25954. /**
  25955. * Getter/setter function for the checked state.
  25956. *
  25957. * @method checked
  25958. * @param {Boolean} [state] State to be set.
  25959. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
  25960. */
  25961. checked: function(state) {
  25962. var self = this;
  25963. if (typeof state != "undefined") {
  25964. if (state) {
  25965. self.addClass('checked');
  25966. } else {
  25967. self.removeClass('checked');
  25968. }
  25969. self._checked = state;
  25970. self.aria('checked', state);
  25971. return self;
  25972. }
  25973. return self._checked;
  25974. },
  25975. /**
  25976. * Getter/setter function for the value state.
  25977. *
  25978. * @method value
  25979. * @param {Boolean} [state] State to be set.
  25980. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
  25981. */
  25982. value: function(state) {
  25983. return this.checked(state);
  25984. },
  25985. /**
  25986. * Renders the control as a HTML string.
  25987. *
  25988. * @method renderHtml
  25989. * @return {String} HTML representing the control.
  25990. */
  25991. renderHtml: function() {
  25992. var self = this, id = self._id, prefix = self.classPrefix;
  25993. return (
  25994. '<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
  25995. '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
  25996. '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
  25997. '</div>'
  25998. );
  25999. }
  26000. });
  26001. });
  26002. // Included from: js/tinymce/classes/ui/PanelButton.js
  26003. /**
  26004. * PanelButton.js
  26005. *
  26006. * Copyright, Moxiecode Systems AB
  26007. * Released under LGPL License.
  26008. *
  26009. * License: http://www.tinymce.com/license
  26010. * Contributing: http://www.tinymce.com/contributing
  26011. */
  26012. /**
  26013. * Creates a new panel button.
  26014. *
  26015. * @class tinymce.ui.PanelButton
  26016. * @extends tinymce.ui.Button
  26017. */
  26018. define("tinymce/ui/PanelButton", [
  26019. "tinymce/ui/Button",
  26020. "tinymce/ui/FloatPanel"
  26021. ], function(Button, FloatPanel) {
  26022. "use strict";
  26023. return Button.extend({
  26024. /**
  26025. * Shows the panel for the button.
  26026. *
  26027. * @method showPanel
  26028. */
  26029. showPanel: function() {
  26030. var self = this, settings = self.settings;
  26031. self.active(true);
  26032. if (!self.panel) {
  26033. var panelSettings = settings.panel;
  26034. // Wrap panel in grid layout if type if specified
  26035. // This makes it possible to add forms or other containers directly in the panel option
  26036. if (panelSettings.type) {
  26037. panelSettings = {
  26038. layout: 'grid',
  26039. items: panelSettings
  26040. };
  26041. }
  26042. panelSettings.role = panelSettings.role || 'dialog';
  26043. panelSettings.popover = true;
  26044. panelSettings.autohide = true;
  26045. panelSettings.ariaRoot = true;
  26046. self.panel = new FloatPanel(panelSettings).on('hide', function() {
  26047. self.active(false);
  26048. }).on('cancel', function(e) {
  26049. e.stopPropagation();
  26050. self.focus();
  26051. self.hidePanel();
  26052. }).parent(self).renderTo(self.getContainerElm());
  26053. self.panel.fire('show');
  26054. self.panel.reflow();
  26055. } else {
  26056. self.panel.show();
  26057. }
  26058. self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
  26059. },
  26060. /**
  26061. * Hides the panel for the button.
  26062. *
  26063. * @method hidePanel
  26064. */
  26065. hidePanel: function() {
  26066. var self = this;
  26067. if (self.panel) {
  26068. self.panel.hide();
  26069. }
  26070. },
  26071. /**
  26072. * Called after the control has been rendered.
  26073. *
  26074. * @method postRender
  26075. */
  26076. postRender: function() {
  26077. var self = this;
  26078. self.aria('haspopup', true);
  26079. self.on('click', function(e) {
  26080. if (e.control === self) {
  26081. if (self.panel && self.panel.visible()) {
  26082. self.hidePanel();
  26083. } else {
  26084. self.showPanel();
  26085. self.panel.focus(!!e.aria);
  26086. }
  26087. }
  26088. });
  26089. return self._super();
  26090. }
  26091. });
  26092. });
  26093. // Included from: js/tinymce/classes/ui/ColorButton.js
  26094. /**
  26095. * ColorButton.js
  26096. *
  26097. * Copyright, Moxiecode Systems AB
  26098. * Released under LGPL License.
  26099. *
  26100. * License: http://www.tinymce.com/license
  26101. * Contributing: http://www.tinymce.com/contributing
  26102. */
  26103. /**
  26104. * This class creates a color button control. This is a split button in which the main
  26105. * button has a visual representation of the currently selected color. When clicked
  26106. * the caret button displays a color picker, allowing the user to select a new color.
  26107. *
  26108. * @-x-less ColorButton.less
  26109. * @class tinymce.ui.ColorButton
  26110. * @extends tinymce.ui.PanelButton
  26111. */
  26112. define("tinymce/ui/ColorButton", [
  26113. "tinymce/ui/PanelButton",
  26114. "tinymce/dom/DOMUtils"
  26115. ], function(PanelButton, DomUtils) {
  26116. "use strict";
  26117. var DOM = DomUtils.DOM;
  26118. return PanelButton.extend({
  26119. /**
  26120. * Constructs a new ColorButton instance with the specified settings.
  26121. *
  26122. * @constructor
  26123. * @param {Object} settings Name/value object with settings.
  26124. */
  26125. init: function(settings) {
  26126. this._super(settings);
  26127. this.addClass('colorbutton');
  26128. },
  26129. /**
  26130. * Getter/setter for the current color.
  26131. *
  26132. * @method color
  26133. * @param {String} [color] Color to set.
  26134. * @return {String|tinymce.ui.ColorButton} Current color or current instance.
  26135. */
  26136. color: function(color) {
  26137. if (color) {
  26138. this._color = color;
  26139. this.getEl('preview').style.backgroundColor = color;
  26140. return this;
  26141. }
  26142. return this._color;
  26143. },
  26144. /**
  26145. * Renders the control as a HTML string.
  26146. *
  26147. * @method renderHtml
  26148. * @return {String} HTML representing the control.
  26149. */
  26150. renderHtml: function() {
  26151. var self = this, id = self._id, prefix = self.classPrefix;
  26152. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  26153. var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
  26154. return (
  26155. '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
  26156. '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
  26157. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  26158. '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
  26159. (self._text ? (icon ? ' ' : '') + (self._text) : '') +
  26160. '</button>' +
  26161. '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  26162. ' <i class="' + prefix + 'caret"></i>' +
  26163. '</button>' +
  26164. '</div>'
  26165. );
  26166. },
  26167. /**
  26168. * Called after the control has been rendered.
  26169. *
  26170. * @method postRender
  26171. */
  26172. postRender: function() {
  26173. var self = this, onClickHandler = self.settings.onclick;
  26174. self.on('click', function(e) {
  26175. if (e.aria && e.aria.key == 'down') {
  26176. return;
  26177. }
  26178. if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
  26179. e.stopImmediatePropagation();
  26180. onClickHandler.call(self, e);
  26181. }
  26182. });
  26183. delete self.settings.onclick;
  26184. return self._super();
  26185. }
  26186. });
  26187. });
  26188. // Included from: js/tinymce/classes/ui/ComboBox.js
  26189. /**
  26190. * ComboBox.js
  26191. *
  26192. * Copyright, Moxiecode Systems AB
  26193. * Released under LGPL License.
  26194. *
  26195. * License: http://www.tinymce.com/license
  26196. * Contributing: http://www.tinymce.com/contributing
  26197. */
  26198. /**
  26199. * This class creates a combobox control. Select box that you select a value from or
  26200. * type a value into.
  26201. *
  26202. * @-x-less ComboBox.less
  26203. * @class tinymce.ui.ComboBox
  26204. * @extends tinymce.ui.Widget
  26205. */
  26206. define("tinymce/ui/ComboBox", [
  26207. "tinymce/ui/Widget",
  26208. "tinymce/ui/Factory",
  26209. "tinymce/ui/DomUtils"
  26210. ], function(Widget, Factory, DomUtils) {
  26211. "use strict";
  26212. return Widget.extend({
  26213. /**
  26214. * Constructs a new control instance with the specified settings.
  26215. *
  26216. * @constructor
  26217. * @param {Object} settings Name/value object with settings.
  26218. * @setting {String} placeholder Placeholder text to display.
  26219. */
  26220. init: function(settings) {
  26221. var self = this;
  26222. self._super(settings);
  26223. self.addClass('combobox');
  26224. self.subinput = true;
  26225. self.ariaTarget = 'inp'; // TODO: Figure out a better way
  26226. settings = self.settings;
  26227. settings.menu = settings.menu || settings.values;
  26228. if (settings.menu) {
  26229. settings.icon = 'caret';
  26230. }
  26231. self.on('click', function(e) {
  26232. var elm = e.target, root = self.getEl();
  26233. while (elm && elm != root) {
  26234. if (elm.id && elm.id.indexOf('-open') != -1) {
  26235. self.fire('action');
  26236. if (settings.menu) {
  26237. self.showMenu();
  26238. if (e.aria) {
  26239. self.menu.items()[0].focus();
  26240. }
  26241. }
  26242. }
  26243. elm = elm.parentNode;
  26244. }
  26245. });
  26246. // TODO: Rework this
  26247. self.on('keydown', function(e) {
  26248. if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
  26249. self.parents().reverse().each(function(ctrl) {
  26250. e.preventDefault();
  26251. self.fire('change');
  26252. if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  26253. ctrl.fire('submit', {data: ctrl.toJSON()});
  26254. return false;
  26255. }
  26256. });
  26257. }
  26258. });
  26259. if (settings.placeholder) {
  26260. self.addClass('placeholder');
  26261. self.on('focusin', function() {
  26262. if (!self._hasOnChange) {
  26263. DomUtils.on(self.getEl('inp'), 'change', function() {
  26264. self.fire('change');
  26265. });
  26266. self._hasOnChange = true;
  26267. }
  26268. if (self.hasClass('placeholder')) {
  26269. self.getEl('inp').value = '';
  26270. self.removeClass('placeholder');
  26271. }
  26272. });
  26273. self.on('focusout', function() {
  26274. if (self.value().length === 0) {
  26275. self.getEl('inp').value = settings.placeholder;
  26276. self.addClass('placeholder');
  26277. }
  26278. });
  26279. }
  26280. },
  26281. showMenu: function() {
  26282. var self = this, settings = self.settings, menu;
  26283. if (!self.menu) {
  26284. menu = settings.menu || [];
  26285. // Is menu array then auto constuct menu control
  26286. if (menu.length) {
  26287. menu = {
  26288. type: 'menu',
  26289. items: menu
  26290. };
  26291. } else {
  26292. menu.type = menu.type || 'menu';
  26293. }
  26294. self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
  26295. self.fire('createmenu');
  26296. self.menu.reflow();
  26297. self.menu.on('cancel', function(e) {
  26298. if (e.control === self.menu) {
  26299. self.focus();
  26300. }
  26301. });
  26302. self.menu.on('show hide', function(e) {
  26303. e.control.items().each(function(ctrl) {
  26304. ctrl.active(ctrl.value() == self.value());
  26305. });
  26306. }).fire('show');
  26307. self.menu.on('select', function(e) {
  26308. self.value(e.control.value());
  26309. });
  26310. self.on('focusin', function(e) {
  26311. if (e.target.tagName.toUpperCase() == 'INPUT') {
  26312. self.menu.hide();
  26313. }
  26314. });
  26315. self.aria('expanded', true);
  26316. }
  26317. self.menu.show();
  26318. self.menu.layoutRect({w: self.layoutRect().w});
  26319. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  26320. },
  26321. /**
  26322. * Getter/setter function for the control value.
  26323. *
  26324. * @method value
  26325. * @param {String} [value] Value to be set.
  26326. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
  26327. */
  26328. value: function(value) {
  26329. var self = this;
  26330. if (typeof(value) != "undefined") {
  26331. self._value = value;
  26332. self.removeClass('placeholder');
  26333. if (self._rendered) {
  26334. self.getEl('inp').value = value;
  26335. }
  26336. return self;
  26337. }
  26338. if (self._rendered) {
  26339. value = self.getEl('inp').value;
  26340. if (value != self.settings.placeholder) {
  26341. return value;
  26342. }
  26343. return '';
  26344. }
  26345. return self._value;
  26346. },
  26347. /**
  26348. * Getter/setter function for the disabled state.
  26349. *
  26350. * @method value
  26351. * @param {Boolean} [state] State to be set.
  26352. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
  26353. */
  26354. disabled: function(state) {
  26355. var self = this;
  26356. if (self._rendered && typeof(state) != 'undefined') {
  26357. self.getEl('inp').disabled = state;
  26358. }
  26359. return self._super(state);
  26360. },
  26361. /**
  26362. * Focuses the input area of the control.
  26363. *
  26364. * @method focus
  26365. */
  26366. focus: function() {
  26367. this.getEl('inp').focus();
  26368. },
  26369. /**
  26370. * Repaints the control after a layout operation.
  26371. *
  26372. * @method repaint
  26373. */
  26374. repaint: function() {
  26375. var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
  26376. var width, lineHeight;
  26377. if (openElm) {
  26378. width = rect.w - DomUtils.getSize(openElm).width - 10;
  26379. } else {
  26380. width = rect.w - 10;
  26381. }
  26382. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  26383. var doc = document;
  26384. if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  26385. lineHeight = (self.layoutRect().h - 2) + 'px';
  26386. }
  26387. DomUtils.css(elm.firstChild, {
  26388. width: width,
  26389. lineHeight: lineHeight
  26390. });
  26391. self._super();
  26392. return self;
  26393. },
  26394. /**
  26395. * Post render method. Called after the control has been rendered to the target.
  26396. *
  26397. * @method postRender
  26398. * @return {tinymce.ui.ComboBox} Current combobox instance.
  26399. */
  26400. postRender: function() {
  26401. var self = this;
  26402. DomUtils.on(this.getEl('inp'), 'change', function() {
  26403. self.fire('change');
  26404. });
  26405. return self._super();
  26406. },
  26407. remove: function() {
  26408. DomUtils.off(this.getEl('inp'));
  26409. this._super();
  26410. },
  26411. /**
  26412. * Renders the control as a HTML string.
  26413. *
  26414. * @method renderHtml
  26415. * @return {String} HTML representing the control.
  26416. */
  26417. renderHtml: function() {
  26418. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
  26419. var value = settings.value || settings.placeholder || '';
  26420. var icon, text, openBtnHtml = '', extraAttrs = '';
  26421. if ("spellcheck" in settings) {
  26422. extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  26423. }
  26424. if (settings.maxLength) {
  26425. extraAttrs += ' maxlength="' + settings.maxLength + '"';
  26426. }
  26427. if (settings.size) {
  26428. extraAttrs += ' size="' + settings.size + '"';
  26429. }
  26430. if (settings.subtype) {
  26431. extraAttrs += ' type="' + settings.subtype + '"';
  26432. }
  26433. if (self.disabled()) {
  26434. extraAttrs += ' disabled="disabled"';
  26435. }
  26436. icon = settings.icon;
  26437. if (icon && icon != 'caret') {
  26438. icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
  26439. }
  26440. text = self._text;
  26441. if (icon || text) {
  26442. openBtnHtml = (
  26443. '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
  26444. '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
  26445. (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
  26446. (text ? (icon ? ' ' : '') + text : '') +
  26447. '</button>' +
  26448. '</div>'
  26449. );
  26450. self.addClass('has-open');
  26451. }
  26452. return (
  26453. '<div id="' + id + '" class="' + self.classes() + '">' +
  26454. '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
  26455. value + '" hidefocus="1"' + extraAttrs + ' />' +
  26456. openBtnHtml +
  26457. '</div>'
  26458. );
  26459. }
  26460. });
  26461. });
  26462. // Included from: js/tinymce/classes/ui/Path.js
  26463. /**
  26464. * Path.js
  26465. *
  26466. * Copyright, Moxiecode Systems AB
  26467. * Released under LGPL License.
  26468. *
  26469. * License: http://www.tinymce.com/license
  26470. * Contributing: http://www.tinymce.com/contributing
  26471. */
  26472. /**
  26473. * Creates a new path control.
  26474. *
  26475. * @-x-less Path.less
  26476. * @class tinymce.ui.Path
  26477. * @extends tinymce.ui.Widget
  26478. */
  26479. define("tinymce/ui/Path", [
  26480. "tinymce/ui/Widget"
  26481. ], function(Widget) {
  26482. "use strict";
  26483. return Widget.extend({
  26484. /**
  26485. * Constructs a instance with the specified settings.
  26486. *
  26487. * @constructor
  26488. * @param {Object} settings Name/value object with settings.
  26489. * @setting {String} delimiter Delimiter to display between items in path.
  26490. */
  26491. init: function(settings) {
  26492. var self = this;
  26493. if (!settings.delimiter) {
  26494. settings.delimiter = '\u00BB';
  26495. }
  26496. self._super(settings);
  26497. self.addClass('path');
  26498. self.canFocus = true;
  26499. self.on('click', function(e) {
  26500. var index, target = e.target;
  26501. if ((index = target.getAttribute('data-index'))) {
  26502. self.fire('select', {value: self.data()[index], index: index});
  26503. }
  26504. });
  26505. },
  26506. /**
  26507. * Focuses the current control.
  26508. *
  26509. * @method focus
  26510. * @return {tinymce.ui.Control} Current control instance.
  26511. */
  26512. focus: function() {
  26513. var self = this;
  26514. self.getEl().firstChild.focus();
  26515. return self;
  26516. },
  26517. /**
  26518. * Sets/gets the data to be used for the path.
  26519. *
  26520. * @method data
  26521. * @param {Array} data Array with items name is rendered to path.
  26522. */
  26523. data: function(data) {
  26524. var self = this;
  26525. if (typeof(data) !== "undefined") {
  26526. self._data = data;
  26527. self.update();
  26528. return self;
  26529. }
  26530. return self._data;
  26531. },
  26532. /**
  26533. * Updated the path.
  26534. *
  26535. * @private
  26536. */
  26537. update: function() {
  26538. this.innerHtml(this._getPathHtml());
  26539. },
  26540. /**
  26541. * Called after the control has been rendered.
  26542. *
  26543. * @method postRender
  26544. */
  26545. postRender: function() {
  26546. var self = this;
  26547. self._super();
  26548. self.data(self.settings.data);
  26549. },
  26550. /**
  26551. * Renders the control as a HTML string.
  26552. *
  26553. * @method renderHtml
  26554. * @return {String} HTML representing the control.
  26555. */
  26556. renderHtml: function() {
  26557. var self = this;
  26558. return (
  26559. '<div id="' + self._id + '" class="' + self.classes() + '">' +
  26560. self._getPathHtml() +
  26561. '</div>'
  26562. );
  26563. },
  26564. _getPathHtml: function() {
  26565. var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;
  26566. for (i = 0, l = parts.length; i < l; i++) {
  26567. html += (
  26568. (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
  26569. '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
  26570. i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
  26571. );
  26572. }
  26573. if (!html) {
  26574. html = '<div class="' + prefix + 'path-item">\u00a0</div>';
  26575. }
  26576. return html;
  26577. }
  26578. });
  26579. });
  26580. // Included from: js/tinymce/classes/ui/ElementPath.js
  26581. /**
  26582. * ElementPath.js
  26583. *
  26584. * Copyright, Moxiecode Systems AB
  26585. * Released under LGPL License.
  26586. *
  26587. * License: http://www.tinymce.com/license
  26588. * Contributing: http://www.tinymce.com/contributing
  26589. */
  26590. /**
  26591. * This control creates an path for the current selections parent elements in TinyMCE.
  26592. *
  26593. * @class tinymce.ui.ElementPath
  26594. * @extends tinymce.ui.Path
  26595. */
  26596. define("tinymce/ui/ElementPath", [
  26597. "tinymce/ui/Path",
  26598. "tinymce/EditorManager"
  26599. ], function(Path, EditorManager) {
  26600. return Path.extend({
  26601. /**
  26602. * Post render method. Called after the control has been rendered to the target.
  26603. *
  26604. * @method postRender
  26605. * @return {tinymce.ui.ElementPath} Current combobox instance.
  26606. */
  26607. postRender: function() {
  26608. var self = this, editor = EditorManager.activeEditor;
  26609. function isHidden(elm) {
  26610. if (elm.nodeType === 1) {
  26611. if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
  26612. return true;
  26613. }
  26614. if (elm.getAttribute('data-mce-type') === 'bookmark') {
  26615. return true;
  26616. }
  26617. }
  26618. return false;
  26619. }
  26620. self.on('select', function(e) {
  26621. var parents = [], node, body = editor.getBody();
  26622. editor.focus();
  26623. node = editor.selection.getStart();
  26624. while (node && node != body) {
  26625. if (!isHidden(node)) {
  26626. parents.push(node);
  26627. }
  26628. node = node.parentNode;
  26629. }
  26630. editor.selection.select(parents[parents.length - 1 - e.index]);
  26631. editor.nodeChanged();
  26632. });
  26633. editor.on('nodeChange', function(e) {
  26634. var parents = [], selectionParents = e.parents, i = selectionParents.length;
  26635. while (i--) {
  26636. if (selectionParents[i].nodeType == 1 && !isHidden(selectionParents[i])) {
  26637. var args = editor.fire('ResolveName', {
  26638. name: selectionParents[i].nodeName.toLowerCase(),
  26639. target: selectionParents[i]
  26640. });
  26641. parents.push({name: args.name});
  26642. }
  26643. }
  26644. self.data(parents);
  26645. });
  26646. return self._super();
  26647. }
  26648. });
  26649. });
  26650. // Included from: js/tinymce/classes/ui/FormItem.js
  26651. /**
  26652. * FormItem.js
  26653. *
  26654. * Copyright, Moxiecode Systems AB
  26655. * Released under LGPL License.
  26656. *
  26657. * License: http://www.tinymce.com/license
  26658. * Contributing: http://www.tinymce.com/contributing
  26659. */
  26660. /**
  26661. * This class is a container created by the form element with
  26662. * a label and control item.
  26663. *
  26664. * @class tinymce.ui.FormItem
  26665. * @extends tinymce.ui.Container
  26666. * @setting {String} label Label to display for the form item.
  26667. */
  26668. define("tinymce/ui/FormItem", [
  26669. "tinymce/ui/Container"
  26670. ], function(Container) {
  26671. "use strict";
  26672. return Container.extend({
  26673. Defaults: {
  26674. layout: 'flex',
  26675. align: 'center',
  26676. defaults: {
  26677. flex: 1
  26678. }
  26679. },
  26680. /**
  26681. * Renders the control as a HTML string.
  26682. *
  26683. * @method renderHtml
  26684. * @return {String} HTML representing the control.
  26685. */
  26686. renderHtml: function() {
  26687. var self = this, layout = self._layout, prefix = self.classPrefix;
  26688. self.addClass('formitem');
  26689. layout.preRender(self);
  26690. return (
  26691. '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  26692. (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
  26693. self.settings.title + '</div>') : '') +
  26694. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  26695. (self.settings.html || '') + layout.renderHtml(self) +
  26696. '</div>' +
  26697. '</div>'
  26698. );
  26699. }
  26700. });
  26701. });
  26702. // Included from: js/tinymce/classes/ui/Form.js
  26703. /**
  26704. * Form.js
  26705. *
  26706. * Copyright, Moxiecode Systems AB
  26707. * Released under LGPL License.
  26708. *
  26709. * License: http://www.tinymce.com/license
  26710. * Contributing: http://www.tinymce.com/contributing
  26711. */
  26712. /**
  26713. * This class creates a form container. A form container has the ability
  26714. * to automatically wrap items in tinymce.ui.FormItem instances.
  26715. *
  26716. * Each FormItem instance is a container for the label and the item.
  26717. *
  26718. * @example
  26719. * tinymce.ui.Factory.create({
  26720. * type: 'form',
  26721. * items: [
  26722. * {type: 'textbox', label: 'My text box'}
  26723. * ]
  26724. * }).renderTo(document.body);
  26725. *
  26726. * @class tinymce.ui.Form
  26727. * @extends tinymce.ui.Container
  26728. */
  26729. define("tinymce/ui/Form", [
  26730. "tinymce/ui/Container",
  26731. "tinymce/ui/FormItem"
  26732. ], function(Container, FormItem) {
  26733. "use strict";
  26734. return Container.extend({
  26735. Defaults: {
  26736. containerCls: 'form',
  26737. layout: 'flex',
  26738. direction: 'column',
  26739. align: 'stretch',
  26740. flex: 1,
  26741. padding: 20,
  26742. labelGap: 30,
  26743. spacing: 10,
  26744. callbacks: {
  26745. submit: function() {
  26746. this.submit();
  26747. }
  26748. }
  26749. },
  26750. /**
  26751. * This method gets invoked before the control is rendered.
  26752. *
  26753. * @method preRender
  26754. */
  26755. preRender: function() {
  26756. var self = this, items = self.items();
  26757. // Wrap any labeled items in FormItems
  26758. items.each(function(ctrl) {
  26759. var formItem, label = ctrl.settings.label;
  26760. if (label) {
  26761. formItem = new FormItem({
  26762. layout: 'flex',
  26763. autoResize: "overflow",
  26764. defaults: {flex: 1},
  26765. items: [
  26766. {type: 'label', id: ctrl._id + '-l', text: label, flex: 0, forId: ctrl._id, disabled: ctrl.disabled()}
  26767. ]
  26768. });
  26769. formItem.type = 'formitem';
  26770. ctrl.aria('labelledby', ctrl._id + '-l');
  26771. if (typeof(ctrl.settings.flex) == "undefined") {
  26772. ctrl.settings.flex = 1;
  26773. }
  26774. self.replace(ctrl, formItem);
  26775. formItem.add(ctrl);
  26776. }
  26777. });
  26778. },
  26779. /**
  26780. * Recalcs label widths.
  26781. *
  26782. * @private
  26783. */
  26784. recalcLabels: function() {
  26785. var self = this, maxLabelWidth = 0, labels = [], i, labelGap;
  26786. if (self.settings.labelGapCalc === false) {
  26787. return;
  26788. }
  26789. self.items().filter('formitem').each(function(item) {
  26790. var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
  26791. maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
  26792. labels.push(labelCtrl);
  26793. });
  26794. labelGap = self.settings.labelGap || 0;
  26795. i = labels.length;
  26796. while (i--) {
  26797. labels[i].settings.minWidth = maxLabelWidth + labelGap;
  26798. }
  26799. },
  26800. /**
  26801. * Getter/setter for the visibility state.
  26802. *
  26803. * @method visible
  26804. * @param {Boolean} [state] True/false state to show/hide.
  26805. * @return {tinymce.ui.Form|Boolean} True/false state or current control.
  26806. */
  26807. visible: function(state) {
  26808. var val = this._super(state);
  26809. if (state === true && this._rendered) {
  26810. this.recalcLabels();
  26811. }
  26812. return val;
  26813. },
  26814. /**
  26815. * Fires a submit event with the serialized form.
  26816. *
  26817. * @method submit
  26818. * @return {Object} Event arguments object.
  26819. */
  26820. submit: function() {
  26821. return this.fire('submit', {data: this.toJSON()});
  26822. },
  26823. /**
  26824. * Post render method. Called after the control has been rendered to the target.
  26825. *
  26826. * @method postRender
  26827. * @return {tinymce.ui.ComboBox} Current combobox instance.
  26828. */
  26829. postRender: function() {
  26830. var self = this;
  26831. self._super();
  26832. self.recalcLabels();
  26833. self.fromJSON(self.settings.data);
  26834. }
  26835. });
  26836. });
  26837. // Included from: js/tinymce/classes/ui/FieldSet.js
  26838. /**
  26839. * FieldSet.js
  26840. *
  26841. * Copyright, Moxiecode Systems AB
  26842. * Released under LGPL License.
  26843. *
  26844. * License: http://www.tinymce.com/license
  26845. * Contributing: http://www.tinymce.com/contributing
  26846. */
  26847. /**
  26848. * This class creates fieldset containers.
  26849. *
  26850. * @-x-less FieldSet.less
  26851. * @class tinymce.ui.FieldSet
  26852. * @extends tinymce.ui.Form
  26853. */
  26854. define("tinymce/ui/FieldSet", [
  26855. "tinymce/ui/Form"
  26856. ], function(Form) {
  26857. "use strict";
  26858. return Form.extend({
  26859. Defaults: {
  26860. containerCls: 'fieldset',
  26861. layout: 'flex',
  26862. direction: 'column',
  26863. align: 'stretch',
  26864. flex: 1,
  26865. padding: "25 15 5 15",
  26866. labelGap: 30,
  26867. spacing: 10,
  26868. border: 1
  26869. },
  26870. /**
  26871. * Renders the control as a HTML string.
  26872. *
  26873. * @method renderHtml
  26874. * @return {String} HTML representing the control.
  26875. */
  26876. renderHtml: function() {
  26877. var self = this, layout = self._layout, prefix = self.classPrefix;
  26878. self.preRender();
  26879. layout.preRender(self);
  26880. return (
  26881. '<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  26882. (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
  26883. self.settings.title + '</legend>') : '') +
  26884. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  26885. (self.settings.html || '') + layout.renderHtml(self) +
  26886. '</div>' +
  26887. '</fieldset>'
  26888. );
  26889. }
  26890. });
  26891. });
  26892. // Included from: js/tinymce/classes/ui/FilePicker.js
  26893. /**
  26894. * FilePicker.js
  26895. *
  26896. * Copyright, Moxiecode Systems AB
  26897. * Released under LGPL License.
  26898. *
  26899. * License: http://www.tinymce.com/license
  26900. * Contributing: http://www.tinymce.com/contributing
  26901. */
  26902. /*global tinymce:true */
  26903. /**
  26904. * This class creates a file picker control.
  26905. *
  26906. * @class tinymce.ui.FilePicker
  26907. * @extends tinymce.ui.ComboBox
  26908. */
  26909. define("tinymce/ui/FilePicker", [
  26910. "tinymce/ui/ComboBox"
  26911. ], function(ComboBox) {
  26912. "use strict";
  26913. return ComboBox.extend({
  26914. /**
  26915. * Constructs a new control instance with the specified settings.
  26916. *
  26917. * @constructor
  26918. * @param {Object} settings Name/value object with settings.
  26919. */
  26920. init: function(settings) {
  26921. var self = this, editor = tinymce.activeEditor, fileBrowserCallback;
  26922. settings.spellcheck = false;
  26923. fileBrowserCallback = editor.settings.file_browser_callback;
  26924. if (fileBrowserCallback) {
  26925. settings.icon = 'browse';
  26926. settings.onaction = function() {
  26927. fileBrowserCallback(
  26928. self.getEl('inp').id,
  26929. self.getEl('inp').value,
  26930. settings.filetype,
  26931. window
  26932. );
  26933. };
  26934. }
  26935. self._super(settings);
  26936. }
  26937. });
  26938. });
  26939. // Included from: js/tinymce/classes/ui/FitLayout.js
  26940. /**
  26941. * FitLayout.js
  26942. *
  26943. * Copyright, Moxiecode Systems AB
  26944. * Released under LGPL License.
  26945. *
  26946. * License: http://www.tinymce.com/license
  26947. * Contributing: http://www.tinymce.com/contributing
  26948. */
  26949. /**
  26950. * This layout manager will resize the control to be the size of it's parent container.
  26951. * In other words width: 100% and height: 100%.
  26952. *
  26953. * @-x-less FitLayout.less
  26954. * @class tinymce.ui.FitLayout
  26955. * @extends tinymce.ui.AbsoluteLayout
  26956. */
  26957. define("tinymce/ui/FitLayout", [
  26958. "tinymce/ui/AbsoluteLayout"
  26959. ], function(AbsoluteLayout) {
  26960. "use strict";
  26961. return AbsoluteLayout.extend({
  26962. /**
  26963. * Recalculates the positions of the controls in the specified container.
  26964. *
  26965. * @method recalc
  26966. * @param {tinymce.ui.Container} container Container instance to recalc.
  26967. */
  26968. recalc: function(container) {
  26969. var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();
  26970. container.items().filter(':visible').each(function(ctrl) {
  26971. ctrl.layoutRect({
  26972. x: paddingBox.left,
  26973. y: paddingBox.top,
  26974. w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
  26975. h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
  26976. });
  26977. if (ctrl.recalc) {
  26978. ctrl.recalc();
  26979. }
  26980. });
  26981. }
  26982. });
  26983. });
  26984. // Included from: js/tinymce/classes/ui/FlexLayout.js
  26985. /**
  26986. * FlexLayout.js
  26987. *
  26988. * Copyright, Moxiecode Systems AB
  26989. * Released under LGPL License.
  26990. *
  26991. * License: http://www.tinymce.com/license
  26992. * Contributing: http://www.tinymce.com/contributing
  26993. */
  26994. /**
  26995. * This layout manager works similar to the CSS flex box.
  26996. *
  26997. * @setting {String} direction row|row-reverse|column|column-reverse
  26998. * @setting {Number} flex A positive-number to flex by.
  26999. * @setting {String} align start|end|center|stretch
  27000. * @setting {String} pack start|end|justify
  27001. *
  27002. * @class tinymce.ui.FlexLayout
  27003. * @extends tinymce.ui.AbsoluteLayout
  27004. */
  27005. define("tinymce/ui/FlexLayout", [
  27006. "tinymce/ui/AbsoluteLayout"
  27007. ], function(AbsoluteLayout) {
  27008. "use strict";
  27009. return AbsoluteLayout.extend({
  27010. /**
  27011. * Recalculates the positions of the controls in the specified container.
  27012. *
  27013. * @method recalc
  27014. * @param {tinymce.ui.Container} container Container instance to recalc.
  27015. */
  27016. recalc: function(container) {
  27017. // A ton of variables, needs to be in the same scope for performance
  27018. var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
  27019. var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
  27020. var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
  27021. var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
  27022. var alignDeltaSizeName, alignContentSizeName;
  27023. var max = Math.max, min = Math.min;
  27024. // Get container items, properties and settings
  27025. items = container.items().filter(':visible');
  27026. contLayoutRect = container.layoutRect();
  27027. contPaddingBox = container._paddingBox;
  27028. contSettings = container.settings;
  27029. direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
  27030. align = contSettings.align;
  27031. pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
  27032. spacing = contSettings.spacing || 0;
  27033. if (direction == "row-reversed" || direction == "column-reverse") {
  27034. items = items.set(items.toArray().reverse());
  27035. direction = direction.split('-')[0];
  27036. }
  27037. // Setup axis variable name for row/column direction since the calculations is the same
  27038. if (direction == "column") {
  27039. posName = "y";
  27040. sizeName = "h";
  27041. minSizeName = "minH";
  27042. maxSizeName = "maxH";
  27043. innerSizeName = "innerH";
  27044. beforeName = 'top';
  27045. deltaSizeName = "deltaH";
  27046. contentSizeName = "contentH";
  27047. alignBeforeName = "left";
  27048. alignSizeName = "w";
  27049. alignAxisName = "x";
  27050. alignInnerSizeName = "innerW";
  27051. alignMinSizeName = "minW";
  27052. alignAfterName = "right";
  27053. alignDeltaSizeName = "deltaW";
  27054. alignContentSizeName = "contentW";
  27055. } else {
  27056. posName = "x";
  27057. sizeName = "w";
  27058. minSizeName = "minW";
  27059. maxSizeName = "maxW";
  27060. innerSizeName = "innerW";
  27061. beforeName = 'left';
  27062. deltaSizeName = "deltaW";
  27063. contentSizeName = "contentW";
  27064. alignBeforeName = "top";
  27065. alignSizeName = "h";
  27066. alignAxisName = "y";
  27067. alignInnerSizeName = "innerH";
  27068. alignMinSizeName = "minH";
  27069. alignAfterName = "bottom";
  27070. alignDeltaSizeName = "deltaH";
  27071. alignContentSizeName = "contentH";
  27072. }
  27073. // Figure out total flex, availableSpace and collect any max size elements
  27074. availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
  27075. maxAlignEndPos = totalFlex = 0;
  27076. for (i = 0, l = items.length; i < l; i++) {
  27077. ctrl = items[i];
  27078. ctrlLayoutRect = ctrl.layoutRect();
  27079. ctrlSettings = ctrl.settings;
  27080. flex = ctrlSettings.flex;
  27081. availableSpace -= (i < l - 1 ? spacing : 0);
  27082. if (flex > 0) {
  27083. totalFlex += flex;
  27084. // Flexed item has a max size then we need to check if we will hit that size
  27085. if (ctrlLayoutRect[maxSizeName]) {
  27086. maxSizeItems.push(ctrl);
  27087. }
  27088. ctrlLayoutRect.flex = flex;
  27089. }
  27090. availableSpace -= ctrlLayoutRect[minSizeName];
  27091. // Calculate the align end position to be used to check for overflow/underflow
  27092. size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
  27093. if (size > maxAlignEndPos) {
  27094. maxAlignEndPos = size;
  27095. }
  27096. }
  27097. // Calculate minW/minH
  27098. rect = {};
  27099. if (availableSpace < 0) {
  27100. rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  27101. } else {
  27102. rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  27103. }
  27104. rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
  27105. rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
  27106. rect[alignContentSizeName] = maxAlignEndPos;
  27107. rect.minW = min(rect.minW, contLayoutRect.maxW);
  27108. rect.minH = min(rect.minH, contLayoutRect.maxH);
  27109. rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
  27110. rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
  27111. // Resize container container if minSize was changed
  27112. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  27113. rect.w = rect.minW;
  27114. rect.h = rect.minH;
  27115. container.layoutRect(rect);
  27116. this.recalc(container);
  27117. // Forced recalc for example if items are hidden/shown
  27118. if (container._lastRect === null) {
  27119. var parentCtrl = container.parent();
  27120. if (parentCtrl) {
  27121. parentCtrl._lastRect = null;
  27122. parentCtrl.recalc();
  27123. }
  27124. }
  27125. return;
  27126. }
  27127. // Handle max size elements, check if they will become to wide with current options
  27128. ratio = availableSpace / totalFlex;
  27129. for (i = 0, l = maxSizeItems.length; i < l; i++) {
  27130. ctrl = maxSizeItems[i];
  27131. ctrlLayoutRect = ctrl.layoutRect();
  27132. maxSize = ctrlLayoutRect[maxSizeName];
  27133. size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
  27134. if (size > maxSize) {
  27135. availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
  27136. totalFlex -= ctrlLayoutRect.flex;
  27137. ctrlLayoutRect.flex = 0;
  27138. ctrlLayoutRect.maxFlexSize = maxSize;
  27139. } else {
  27140. ctrlLayoutRect.maxFlexSize = 0;
  27141. }
  27142. }
  27143. // Setup new ratio, target layout rect, start position
  27144. ratio = availableSpace / totalFlex;
  27145. pos = contPaddingBox[beforeName];
  27146. rect = {};
  27147. // Handle pack setting moves the start position to end, center
  27148. if (totalFlex === 0) {
  27149. if (pack == "end") {
  27150. pos = availableSpace + contPaddingBox[beforeName];
  27151. } else if (pack == "center") {
  27152. pos = Math.round(
  27153. (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
  27154. ) + contPaddingBox[beforeName];
  27155. if (pos < 0) {
  27156. pos = contPaddingBox[beforeName];
  27157. }
  27158. } else if (pack == "justify") {
  27159. pos = contPaddingBox[beforeName];
  27160. spacing = Math.floor(availableSpace / (items.length - 1));
  27161. }
  27162. }
  27163. // Default aligning (start) the other ones needs to be calculated while doing the layout
  27164. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  27165. // Start laying out controls
  27166. for (i = 0, l = items.length; i < l; i++) {
  27167. ctrl = items[i];
  27168. ctrlLayoutRect = ctrl.layoutRect();
  27169. size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
  27170. // Align the control on the other axis
  27171. if (align === "center") {
  27172. rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
  27173. } else if (align === "stretch") {
  27174. rect[alignSizeName] = max(
  27175. ctrlLayoutRect[alignMinSizeName] || 0,
  27176. contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
  27177. );
  27178. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  27179. } else if (align === "end") {
  27180. rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
  27181. }
  27182. // Calculate new size based on flex
  27183. if (ctrlLayoutRect.flex > 0) {
  27184. size += ctrlLayoutRect.flex * ratio;
  27185. }
  27186. rect[sizeName] = size;
  27187. rect[posName] = pos;
  27188. ctrl.layoutRect(rect);
  27189. // Recalculate containers
  27190. if (ctrl.recalc) {
  27191. ctrl.recalc();
  27192. }
  27193. // Move x/y position
  27194. pos += size + spacing;
  27195. }
  27196. }
  27197. });
  27198. });
  27199. // Included from: js/tinymce/classes/ui/FlowLayout.js
  27200. /**
  27201. * FlowLayout.js
  27202. *
  27203. * Copyright, Moxiecode Systems AB
  27204. * Released under LGPL License.
  27205. *
  27206. * License: http://www.tinymce.com/license
  27207. * Contributing: http://www.tinymce.com/contributing
  27208. */
  27209. /**
  27210. * This layout manager will place the controls by using the browsers native layout.
  27211. *
  27212. * @-x-less FlowLayout.less
  27213. * @class tinymce.ui.FlowLayout
  27214. * @extends tinymce.ui.Layout
  27215. */
  27216. define("tinymce/ui/FlowLayout", [
  27217. "tinymce/ui/Layout"
  27218. ], function(Layout) {
  27219. return Layout.extend({
  27220. Defaults: {
  27221. containerClass: 'flow-layout',
  27222. controlClass: 'flow-layout-item',
  27223. endClass : 'break'
  27224. },
  27225. /**
  27226. * Recalculates the positions of the controls in the specified container.
  27227. *
  27228. * @method recalc
  27229. * @param {tinymce.ui.Container} container Container instance to recalc.
  27230. */
  27231. recalc: function(container) {
  27232. container.items().filter(':visible').each(function(ctrl) {
  27233. if (ctrl.recalc) {
  27234. ctrl.recalc();
  27235. }
  27236. });
  27237. }
  27238. });
  27239. });
  27240. // Included from: js/tinymce/classes/ui/FormatControls.js
  27241. /**
  27242. * FormatControls.js
  27243. *
  27244. * Copyright, Moxiecode Systems AB
  27245. * Released under LGPL License.
  27246. *
  27247. * License: http://www.tinymce.com/license
  27248. * Contributing: http://www.tinymce.com/contributing
  27249. */
  27250. /**
  27251. * Internal class containing all TinyMCE specific control types such as
  27252. * format listboxes, fontlist boxes, toolbar buttons etc.
  27253. *
  27254. * @class tinymce.ui.FormatControls
  27255. */
  27256. define("tinymce/ui/FormatControls", [
  27257. "tinymce/ui/Control",
  27258. "tinymce/ui/Widget",
  27259. "tinymce/ui/FloatPanel",
  27260. "tinymce/util/Tools",
  27261. "tinymce/EditorManager",
  27262. "tinymce/Env"
  27263. ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
  27264. var each = Tools.each;
  27265. EditorManager.on('AddEditor', function(e) {
  27266. if (e.editor.rtl) {
  27267. Control.rtl = true;
  27268. }
  27269. registerControls(e.editor);
  27270. });
  27271. Control.translate = function(text) {
  27272. return EditorManager.translate(text);
  27273. };
  27274. Widget.tooltips = !Env.iOS;
  27275. function registerControls(editor) {
  27276. var formatMenu;
  27277. function createListBoxChangeHandler(items, formatName) {
  27278. return function() {
  27279. var self = this;
  27280. editor.on('nodeChange', function(e) {
  27281. var formatter = editor.formatter;
  27282. var value = null;
  27283. each(e.parents, function(node) {
  27284. each(items, function(item) {
  27285. if (formatName) {
  27286. if (formatter.matchNode(node, formatName, {value: item.value})) {
  27287. value = item.value;
  27288. }
  27289. } else {
  27290. if (formatter.matchNode(node, item.value)) {
  27291. value = item.value;
  27292. }
  27293. }
  27294. if (value) {
  27295. return false;
  27296. }
  27297. });
  27298. if (value) {
  27299. return false;
  27300. }
  27301. });
  27302. self.value(value);
  27303. });
  27304. };
  27305. }
  27306. function createFormats(formats) {
  27307. formats = formats.replace(/;$/, '').split(';');
  27308. var i = formats.length;
  27309. while (i--) {
  27310. formats[i] = formats[i].split('=');
  27311. }
  27312. return formats;
  27313. }
  27314. function createFormatMenu() {
  27315. var count = 0, newFormats = [];
  27316. var defaultStyleFormats = [
  27317. {title: 'Headings', items: [
  27318. {title: 'Heading 1', format: 'h1'},
  27319. {title: 'Heading 2', format: 'h2'},
  27320. {title: 'Heading 3', format: 'h3'},
  27321. {title: 'Heading 4', format: 'h4'},
  27322. {title: 'Heading 5', format: 'h5'},
  27323. {title: 'Heading 6', format: 'h6'}
  27324. ]},
  27325. {title: 'Inline', items: [
  27326. {title: 'Bold', icon: 'bold', format: 'bold'},
  27327. {title: 'Italic', icon: 'italic', format: 'italic'},
  27328. {title: 'Underline', icon: 'underline', format: 'underline'},
  27329. {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
  27330. {title: 'Superscript', icon: 'superscript', format: 'superscript'},
  27331. {title: 'Subscript', icon: 'subscript', format: 'subscript'},
  27332. {title: 'Code', icon: 'code', format: 'code'}
  27333. ]},
  27334. {title: 'Blocks', items: [
  27335. {title: 'Paragraph', format: 'p'},
  27336. {title: 'Blockquote', format: 'blockquote'},
  27337. {title: 'Div', format: 'div'},
  27338. {title: 'Pre', format: 'pre'}
  27339. ]},
  27340. {title: 'Alignment', items: [
  27341. {title: 'Left', icon: 'alignleft', format: 'alignleft'},
  27342. {title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
  27343. {title: 'Right', icon: 'alignright', format: 'alignright'},
  27344. {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
  27345. ]}
  27346. ];
  27347. function createMenu(formats) {
  27348. var menu = [];
  27349. if (!formats) {
  27350. return;
  27351. }
  27352. each(formats, function(format) {
  27353. var menuItem = {
  27354. text: format.title,
  27355. icon: format.icon
  27356. };
  27357. if (format.items) {
  27358. menuItem.menu = createMenu(format.items);
  27359. } else {
  27360. var formatName = format.format || "custom" + count++;
  27361. if (!format.format) {
  27362. format.name = formatName;
  27363. newFormats.push(format);
  27364. }
  27365. menuItem.format = formatName;
  27366. }
  27367. menu.push(menuItem);
  27368. });
  27369. return menu;
  27370. }
  27371. function createStylesMenu() {
  27372. var menu;
  27373. if (editor.settings.style_formats_merge) {
  27374. if (editor.settings.style_formats) {
  27375. menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
  27376. } else {
  27377. menu = createMenu(defaultStyleFormats);
  27378. }
  27379. } else {
  27380. menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
  27381. }
  27382. return menu;
  27383. }
  27384. editor.on('init', function() {
  27385. each(newFormats, function(format) {
  27386. editor.formatter.register(format.name, format);
  27387. });
  27388. });
  27389. return {
  27390. type: 'menu',
  27391. items: createStylesMenu(),
  27392. onPostRender: function(e) {
  27393. editor.fire('renderFormatsMenu', {control: e.control});
  27394. },
  27395. itemDefaults: {
  27396. preview: true,
  27397. textStyle: function() {
  27398. if (this.settings.format) {
  27399. return editor.formatter.getCssText(this.settings.format);
  27400. }
  27401. },
  27402. onPostRender: function() {
  27403. var self = this, formatName = this.settings.format;
  27404. if (formatName) {
  27405. self.parent().on('show', function() {
  27406. self.disabled(!editor.formatter.canApply(formatName));
  27407. self.active(editor.formatter.match(formatName));
  27408. });
  27409. }
  27410. },
  27411. onclick: function() {
  27412. if (this.settings.format) {
  27413. toggleFormat(this.settings.format);
  27414. }
  27415. }
  27416. }
  27417. };
  27418. }
  27419. formatMenu = createFormatMenu();
  27420. // Simple format controls <control/format>:<UI text>
  27421. each({
  27422. bold: 'Bold',
  27423. italic: 'Italic',
  27424. underline: 'Underline',
  27425. strikethrough: 'Strikethrough',
  27426. subscript: 'Subscript',
  27427. superscript: 'Superscript'
  27428. }, function(text, name) {
  27429. editor.addButton(name, {
  27430. tooltip: text,
  27431. onPostRender: function() {
  27432. var self = this;
  27433. // TODO: Fix this
  27434. if (editor.formatter) {
  27435. editor.formatter.formatChanged(name, function(state) {
  27436. self.active(state);
  27437. });
  27438. } else {
  27439. editor.on('init', function() {
  27440. editor.formatter.formatChanged(name, function(state) {
  27441. self.active(state);
  27442. });
  27443. });
  27444. }
  27445. },
  27446. onclick: function() {
  27447. toggleFormat(name);
  27448. }
  27449. });
  27450. });
  27451. // Simple command controls <control>:[<UI text>,<Command>]
  27452. each({
  27453. outdent: ['Decrease indent', 'Outdent'],
  27454. indent: ['Increase indent', 'Indent'],
  27455. cut: ['Cut', 'Cut'],
  27456. copy: ['Copy', 'Copy'],
  27457. paste: ['Paste', 'Paste'],
  27458. help: ['Help', 'mceHelp'],
  27459. selectall: ['Select all', 'SelectAll'],
  27460. hr: ['Insert horizontal rule', 'InsertHorizontalRule'],
  27461. removeformat: ['Clear formatting', 'RemoveFormat'],
  27462. visualaid: ['Visual aids', 'mceToggleVisualAid'],
  27463. newdocument: ['New document', 'mceNewDocument']
  27464. }, function(item, name) {
  27465. editor.addButton(name, {
  27466. tooltip: item[0],
  27467. cmd: item[1]
  27468. });
  27469. });
  27470. // Simple command controls with format state
  27471. each({
  27472. blockquote: ['Blockquote', 'mceBlockQuote'],
  27473. numlist: ['Numbered list', 'InsertOrderedList'],
  27474. bullist: ['Bullet list', 'InsertUnorderedList'],
  27475. subscript: ['Subscript', 'Subscript'],
  27476. superscript: ['Superscript', 'Superscript'],
  27477. alignleft: ['Align left', 'JustifyLeft'],
  27478. aligncenter: ['Align center', 'JustifyCenter'],
  27479. alignright: ['Align right', 'JustifyRight'],
  27480. alignjustify: ['Justify', 'JustifyFull']
  27481. }, function(item, name) {
  27482. editor.addButton(name, {
  27483. tooltip: item[0],
  27484. cmd: item[1],
  27485. onPostRender: function() {
  27486. var self = this;
  27487. // TODO: Fix this
  27488. if (editor.formatter) {
  27489. editor.formatter.formatChanged(name, function(state) {
  27490. self.active(state);
  27491. });
  27492. } else {
  27493. editor.on('init', function() {
  27494. editor.formatter.formatChanged(name, function(state) {
  27495. self.active(state);
  27496. });
  27497. });
  27498. }
  27499. }
  27500. });
  27501. });
  27502. function hasUndo() {
  27503. return editor.undoManager ? editor.undoManager.hasUndo() : false;
  27504. }
  27505. function hasRedo() {
  27506. return editor.undoManager ? editor.undoManager.hasRedo() : false;
  27507. }
  27508. function toggleUndoState() {
  27509. var self = this;
  27510. self.disabled(!hasUndo());
  27511. editor.on('Undo Redo AddUndo TypingUndo', function() {
  27512. self.disabled(!hasUndo());
  27513. });
  27514. }
  27515. function toggleRedoState() {
  27516. var self = this;
  27517. self.disabled(!hasRedo());
  27518. editor.on('Undo Redo AddUndo TypingUndo', function() {
  27519. self.disabled(!hasRedo());
  27520. });
  27521. }
  27522. function toggleVisualAidState() {
  27523. var self = this;
  27524. editor.on('VisualAid', function(e) {
  27525. self.active(e.hasVisual);
  27526. });
  27527. self.active(editor.hasVisual);
  27528. }
  27529. editor.addButton('undo', {
  27530. tooltip: 'Undo',
  27531. onPostRender: toggleUndoState,
  27532. cmd: 'undo'
  27533. });
  27534. editor.addButton('redo', {
  27535. tooltip: 'Redo',
  27536. onPostRender: toggleRedoState,
  27537. cmd: 'redo'
  27538. });
  27539. editor.addMenuItem('newdocument', {
  27540. text: 'New document',
  27541. shortcut: 'Ctrl+N',
  27542. icon: 'newdocument',
  27543. cmd: 'mceNewDocument'
  27544. });
  27545. editor.addMenuItem('undo', {
  27546. text: 'Undo',
  27547. icon: 'undo',
  27548. shortcut: 'Ctrl+Z',
  27549. onPostRender: toggleUndoState,
  27550. cmd: 'undo'
  27551. });
  27552. editor.addMenuItem('redo', {
  27553. text: 'Redo',
  27554. icon: 'redo',
  27555. shortcut: 'Ctrl+Y',
  27556. onPostRender: toggleRedoState,
  27557. cmd: 'redo'
  27558. });
  27559. editor.addMenuItem('visualaid', {
  27560. text: 'Visual aids',
  27561. selectable: true,
  27562. onPostRender: toggleVisualAidState,
  27563. cmd: 'mceToggleVisualAid'
  27564. });
  27565. each({
  27566. cut: ['Cut', 'Cut', 'Ctrl+X'],
  27567. copy: ['Copy', 'Copy', 'Ctrl+C'],
  27568. paste: ['Paste', 'Paste', 'Ctrl+V'],
  27569. selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
  27570. bold: ['Bold', 'Bold', 'Ctrl+B'],
  27571. italic: ['Italic', 'Italic', 'Ctrl+I'],
  27572. underline: ['Underline', 'Underline'],
  27573. strikethrough: ['Strikethrough', 'Strikethrough'],
  27574. subscript: ['Subscript', 'Subscript'],
  27575. superscript: ['Superscript', 'Superscript'],
  27576. removeformat: ['Clear formatting', 'RemoveFormat']
  27577. }, function(item, name) {
  27578. editor.addMenuItem(name, {
  27579. text: item[0],
  27580. icon: name,
  27581. shortcut: item[2],
  27582. cmd: item[1]
  27583. });
  27584. });
  27585. editor.on('mousedown', function() {
  27586. FloatPanel.hideAll();
  27587. });
  27588. function toggleFormat(fmt) {
  27589. if (fmt.control) {
  27590. fmt = fmt.control.value();
  27591. }
  27592. if (fmt) {
  27593. editor.execCommand('mceToggleFormat', false, fmt);
  27594. }
  27595. }
  27596. editor.addButton('styleselect', {
  27597. type: 'menubutton',
  27598. text: 'Formats',
  27599. menu: formatMenu
  27600. });
  27601. editor.addButton('formatselect', function() {
  27602. var items = [], blocks = createFormats(editor.settings.block_formats ||
  27603. 'Paragraph=p;' +
  27604. 'Address=address;' +
  27605. 'Pre=pre;' +
  27606. 'Heading 1=h1;' +
  27607. 'Heading 2=h2;' +
  27608. 'Heading 3=h3;' +
  27609. 'Heading 4=h4;' +
  27610. 'Heading 5=h5;' +
  27611. 'Heading 6=h6'
  27612. );
  27613. each(blocks, function(block) {
  27614. items.push({
  27615. text: block[0],
  27616. value: block[1],
  27617. textStyle: function() {
  27618. return editor.formatter.getCssText(block[1]);
  27619. }
  27620. });
  27621. });
  27622. return {
  27623. type: 'listbox',
  27624. text: blocks[0][0],
  27625. values: items,
  27626. fixedWidth: true,
  27627. onselect: toggleFormat,
  27628. onPostRender: createListBoxChangeHandler(items)
  27629. };
  27630. });
  27631. editor.addButton('fontselect', function() {
  27632. var defaultFontsFormats =
  27633. 'Andale Mono=andale mono,times;' +
  27634. 'Arial=arial,helvetica,sans-serif;' +
  27635. 'Arial Black=arial black,avant garde;' +
  27636. 'Book Antiqua=book antiqua,palatino;' +
  27637. 'Comic Sans MS=comic sans ms,sans-serif;' +
  27638. 'Courier New=courier new,courier;' +
  27639. 'Georgia=georgia,palatino;' +
  27640. 'Helvetica=helvetica;' +
  27641. 'Impact=impact,chicago;' +
  27642. 'Symbol=symbol;' +
  27643. 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
  27644. 'Terminal=terminal,monaco;' +
  27645. 'Times New Roman=times new roman,times;' +
  27646. 'Trebuchet MS=trebuchet ms,geneva;' +
  27647. 'Verdana=verdana,geneva;' +
  27648. 'Webdings=webdings;' +
  27649. 'Wingdings=wingdings,zapf dingbats';
  27650. var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
  27651. each(fonts, function(font) {
  27652. items.push({
  27653. text: {raw: font[0]},
  27654. value: font[1],
  27655. textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
  27656. });
  27657. });
  27658. return {
  27659. type: 'listbox',
  27660. text: 'Font Family',
  27661. tooltip: 'Font Family',
  27662. values: items,
  27663. fixedWidth: true,
  27664. onPostRender: createListBoxChangeHandler(items, 'fontname'),
  27665. onselect: function(e) {
  27666. if (e.control.settings.value) {
  27667. editor.execCommand('FontName', false, e.control.settings.value);
  27668. }
  27669. }
  27670. };
  27671. });
  27672. editor.addButton('fontsizeselect', function() {
  27673. var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
  27674. var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
  27675. each(fontsize_formats.split(' '), function(item) {
  27676. items.push({text: item, value: item});
  27677. });
  27678. return {
  27679. type: 'listbox',
  27680. text: 'Font Sizes',
  27681. tooltip: 'Font Sizes',
  27682. values: items,
  27683. fixedWidth: true,
  27684. onPostRender: createListBoxChangeHandler(items, 'fontsize'),
  27685. onclick: function(e) {
  27686. if (e.control.settings.value) {
  27687. editor.execCommand('FontSize', false, e.control.settings.value);
  27688. }
  27689. }
  27690. };
  27691. });
  27692. editor.addMenuItem('formats', {
  27693. text: 'Formats',
  27694. menu: formatMenu
  27695. });
  27696. }
  27697. });
  27698. // Included from: js/tinymce/classes/ui/GridLayout.js
  27699. /**
  27700. * GridLayout.js
  27701. *
  27702. * Copyright, Moxiecode Systems AB
  27703. * Released under LGPL License.
  27704. *
  27705. * License: http://www.tinymce.com/license
  27706. * Contributing: http://www.tinymce.com/contributing
  27707. */
  27708. /**
  27709. * This layout manager places controls in a grid.
  27710. *
  27711. * @setting {Number} spacing Spacing between controls.
  27712. * @setting {Number} spacingH Horizontal spacing between controls.
  27713. * @setting {Number} spacingV Vertical spacing between controls.
  27714. * @setting {Number} columns Number of columns to use.
  27715. * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
  27716. * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
  27717. * @setting {String} pack start|end
  27718. *
  27719. * @class tinymce.ui.GridLayout
  27720. * @extends tinymce.ui.AbsoluteLayout
  27721. */
  27722. define("tinymce/ui/GridLayout", [
  27723. "tinymce/ui/AbsoluteLayout"
  27724. ], function(AbsoluteLayout) {
  27725. "use strict";
  27726. return AbsoluteLayout.extend({
  27727. /**
  27728. * Recalculates the positions of the controls in the specified container.
  27729. *
  27730. * @method recalc
  27731. * @param {tinymce.ui.Container} container Container instance to recalc.
  27732. */
  27733. recalc: function(container) {
  27734. var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
  27735. ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
  27736. colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight;
  27737. // Get layout settings
  27738. settings = container.settings;
  27739. items = container.items().filter(':visible');
  27740. contLayoutRect = container.layoutRect();
  27741. cols = settings.columns || Math.ceil(Math.sqrt(items.length));
  27742. rows = Math.ceil(items.length / cols);
  27743. spacingH = settings.spacingH || settings.spacing || 0;
  27744. spacingV = settings.spacingV || settings.spacing || 0;
  27745. alignH = settings.alignH || settings.align;
  27746. alignV = settings.alignV || settings.align;
  27747. contPaddingBox = container._paddingBox;
  27748. if (alignH && typeof(alignH) == "string") {
  27749. alignH = [alignH];
  27750. }
  27751. if (alignV && typeof(alignV) == "string") {
  27752. alignV = [alignV];
  27753. }
  27754. // Zero padd columnWidths
  27755. for (x = 0; x < cols; x++) {
  27756. colWidths.push(0);
  27757. }
  27758. // Zero padd rowHeights
  27759. for (y = 0; y < rows; y++) {
  27760. rowHeights.push(0);
  27761. }
  27762. // Calculate columnWidths and rowHeights
  27763. for (y = 0; y < rows; y++) {
  27764. for (x = 0; x < cols; x++) {
  27765. ctrl = items[y * cols + x];
  27766. // Out of bounds
  27767. if (!ctrl) {
  27768. break;
  27769. }
  27770. ctrlLayoutRect = ctrl.layoutRect();
  27771. ctrlMinWidth = ctrlLayoutRect.minW;
  27772. ctrlMinHeight = ctrlLayoutRect.minH;
  27773. colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
  27774. rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
  27775. }
  27776. }
  27777. // Calculate maxX
  27778. availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
  27779. for (maxX = 0, x = 0; x < cols; x++) {
  27780. maxX += colWidths[x] + (x > 0 ? spacingH : 0);
  27781. availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
  27782. }
  27783. // Calculate maxY
  27784. availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
  27785. for (maxY = 0, y = 0; y < rows; y++) {
  27786. maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
  27787. availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
  27788. }
  27789. maxX += contPaddingBox.left + contPaddingBox.right;
  27790. maxY += contPaddingBox.top + contPaddingBox.bottom;
  27791. // Calculate minW/minH
  27792. rect = {};
  27793. rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
  27794. rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
  27795. rect.contentW = rect.minW - contLayoutRect.deltaW;
  27796. rect.contentH = rect.minH - contLayoutRect.deltaH;
  27797. rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
  27798. rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
  27799. rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
  27800. rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
  27801. // Resize container container if minSize was changed
  27802. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  27803. rect.w = rect.minW;
  27804. rect.h = rect.minH;
  27805. container.layoutRect(rect);
  27806. this.recalc(container);
  27807. // Forced recalc for example if items are hidden/shown
  27808. if (container._lastRect === null) {
  27809. var parentCtrl = container.parent();
  27810. if (parentCtrl) {
  27811. parentCtrl._lastRect = null;
  27812. parentCtrl.recalc();
  27813. }
  27814. }
  27815. return;
  27816. }
  27817. // Update contentW/contentH so absEnd moves correctly
  27818. if (contLayoutRect.autoResize) {
  27819. rect = container.layoutRect(rect);
  27820. rect.contentW = rect.minW - contLayoutRect.deltaW;
  27821. rect.contentH = rect.minH - contLayoutRect.deltaH;
  27822. }
  27823. var flexV;
  27824. if (settings.packV == 'start') {
  27825. flexV = 0;
  27826. } else {
  27827. flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
  27828. }
  27829. // Calculate totalFlex
  27830. var totalFlex = 0;
  27831. var flexWidths = settings.flexWidths;
  27832. if (flexWidths) {
  27833. for (x = 0; x < flexWidths.length; x++) {
  27834. totalFlex += flexWidths[x];
  27835. }
  27836. } else {
  27837. totalFlex = cols;
  27838. }
  27839. // Calculate new column widths based on flex values
  27840. var ratio = availableWidth / totalFlex;
  27841. for (x = 0; x < cols; x++) {
  27842. colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
  27843. }
  27844. // Move/resize controls
  27845. posY = contPaddingBox.top;
  27846. for (y = 0; y < rows; y++) {
  27847. posX = contPaddingBox.left;
  27848. height = rowHeights[y] + flexV;
  27849. for (x = 0; x < cols; x++) {
  27850. ctrl = items[y * cols + x];
  27851. // No more controls to render then break
  27852. if (!ctrl) {
  27853. break;
  27854. }
  27855. // Get control settings and calculate x, y
  27856. ctrlSettings = ctrl.settings;
  27857. ctrlLayoutRect = ctrl.layoutRect();
  27858. width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
  27859. ctrlLayoutRect.x = posX;
  27860. ctrlLayoutRect.y = posY;
  27861. // Align control horizontal
  27862. align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
  27863. if (align == "center") {
  27864. ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
  27865. } else if (align == "right") {
  27866. ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
  27867. } else if (align == "stretch") {
  27868. ctrlLayoutRect.w = width;
  27869. }
  27870. // Align control vertical
  27871. align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
  27872. if (align == "center") {
  27873. ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
  27874. } else if (align == "bottom") {
  27875. ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
  27876. } else if (align == "stretch") {
  27877. ctrlLayoutRect.h = height;
  27878. }
  27879. ctrl.layoutRect(ctrlLayoutRect);
  27880. posX += width + spacingH;
  27881. if (ctrl.recalc) {
  27882. ctrl.recalc();
  27883. }
  27884. }
  27885. posY += height + spacingV;
  27886. }
  27887. }
  27888. });
  27889. });
  27890. // Included from: js/tinymce/classes/ui/Iframe.js
  27891. /**
  27892. * Iframe.js
  27893. *
  27894. * Copyright, Moxiecode Systems AB
  27895. * Released under LGPL License.
  27896. *
  27897. * License: http://www.tinymce.com/license
  27898. * Contributing: http://www.tinymce.com/contributing
  27899. */
  27900. /*jshint scripturl:true */
  27901. /**
  27902. * This class creates an iframe.
  27903. *
  27904. * @setting {String} url Url to open in the iframe.
  27905. *
  27906. * @-x-less Iframe.less
  27907. * @class tinymce.ui.Iframe
  27908. * @extends tinymce.ui.Widget
  27909. */
  27910. define("tinymce/ui/Iframe", [
  27911. "tinymce/ui/Widget"
  27912. ], function(Widget) {
  27913. "use strict";
  27914. return Widget.extend({
  27915. /**
  27916. * Renders the control as a HTML string.
  27917. *
  27918. * @method renderHtml
  27919. * @return {String} HTML representing the control.
  27920. */
  27921. renderHtml: function() {
  27922. var self = this;
  27923. self.addClass('iframe');
  27924. self.canFocus = false;
  27925. /*eslint no-script-url:0 */
  27926. return (
  27927. '<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
  27928. (self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
  27929. );
  27930. },
  27931. /**
  27932. * Setter for the iframe source.
  27933. *
  27934. * @method src
  27935. * @param {String} src Source URL for iframe.
  27936. */
  27937. src: function(src) {
  27938. this.getEl().src = src;
  27939. },
  27940. /**
  27941. * Inner HTML for the iframe.
  27942. *
  27943. * @method html
  27944. * @param {String} html HTML string to set as HTML inside the iframe.
  27945. * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
  27946. * @return {tinymce.ui.Iframe} Current iframe control.
  27947. */
  27948. html: function(html, callback) {
  27949. var self = this, body = this.getEl().contentWindow.document.body;
  27950. // Wait for iframe to initialize IE 10 takes time
  27951. if (!body) {
  27952. setTimeout(function() {
  27953. self.html(html);
  27954. }, 0);
  27955. } else {
  27956. body.innerHTML = html;
  27957. if (callback) {
  27958. callback();
  27959. }
  27960. }
  27961. return this;
  27962. }
  27963. });
  27964. });
  27965. // Included from: js/tinymce/classes/ui/Label.js
  27966. /**
  27967. * Label.js
  27968. *
  27969. * Copyright, Moxiecode Systems AB
  27970. * Released under LGPL License.
  27971. *
  27972. * License: http://www.tinymce.com/license
  27973. * Contributing: http://www.tinymce.com/contributing
  27974. */
  27975. /**
  27976. * This class creates a label element. A label is a simple text control
  27977. * that can be bound to other controls.
  27978. *
  27979. * @-x-less Label.less
  27980. * @class tinymce.ui.Label
  27981. * @extends tinymce.ui.Widget
  27982. */
  27983. define("tinymce/ui/Label", [
  27984. "tinymce/ui/Widget",
  27985. "tinymce/ui/DomUtils"
  27986. ], function(Widget, DomUtils) {
  27987. "use strict";
  27988. return Widget.extend({
  27989. /**
  27990. * Constructs a instance with the specified settings.
  27991. *
  27992. * @constructor
  27993. * @param {Object} settings Name/value object with settings.
  27994. * @param {Boolean} multiline Multiline label.
  27995. */
  27996. init: function(settings) {
  27997. var self = this;
  27998. self._super(settings);
  27999. self.addClass('widget');
  28000. self.addClass('label');
  28001. self.canFocus = false;
  28002. if (settings.multiline) {
  28003. self.addClass('autoscroll');
  28004. }
  28005. if (settings.strong) {
  28006. self.addClass('strong');
  28007. }
  28008. },
  28009. /**
  28010. * Initializes the current controls layout rect.
  28011. * This will be executed by the layout managers to determine the
  28012. * default minWidth/minHeight etc.
  28013. *
  28014. * @method initLayoutRect
  28015. * @return {Object} Layout rect instance.
  28016. */
  28017. initLayoutRect: function() {
  28018. var self = this, layoutRect = self._super();
  28019. if (self.settings.multiline) {
  28020. var size = DomUtils.getSize(self.getEl());
  28021. // Check if the text fits within maxW if not then try word wrapping it
  28022. if (size.width > layoutRect.maxW) {
  28023. layoutRect.minW = layoutRect.maxW;
  28024. self.addClass('multiline');
  28025. }
  28026. self.getEl().style.width = layoutRect.minW + 'px';
  28027. layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
  28028. }
  28029. return layoutRect;
  28030. },
  28031. /**
  28032. * Repaints the control after a layout operation.
  28033. *
  28034. * @method repaint
  28035. */
  28036. repaint: function() {
  28037. var self = this;
  28038. if (!self.settings.multiline) {
  28039. self.getEl().style.lineHeight = self.layoutRect().h + 'px';
  28040. }
  28041. return self._super();
  28042. },
  28043. /**
  28044. * Sets/gets the current label text.
  28045. *
  28046. * @method text
  28047. * @param {String} [text] New label text.
  28048. * @return {String|tinymce.ui.Label} Current text or current label instance.
  28049. */
  28050. text: function(text) {
  28051. var self = this;
  28052. if (self._rendered && text) {
  28053. this.innerHtml(self.encode(text));
  28054. }
  28055. return self._super(text);
  28056. },
  28057. /**
  28058. * Renders the control as a HTML string.
  28059. *
  28060. * @method renderHtml
  28061. * @return {String} HTML representing the control.
  28062. */
  28063. renderHtml: function() {
  28064. var self = this, forId = self.settings.forId;
  28065. return (
  28066. '<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
  28067. self.encode(self._text) +
  28068. '</label>'
  28069. );
  28070. }
  28071. });
  28072. });
  28073. // Included from: js/tinymce/classes/ui/Toolbar.js
  28074. /**
  28075. * Toolbar.js
  28076. *
  28077. * Copyright, Moxiecode Systems AB
  28078. * Released under LGPL License.
  28079. *
  28080. * License: http://www.tinymce.com/license
  28081. * Contributing: http://www.tinymce.com/contributing
  28082. */
  28083. /**
  28084. * Creates a new toolbar.
  28085. *
  28086. * @class tinymce.ui.Toolbar
  28087. * @extends tinymce.ui.Container
  28088. */
  28089. define("tinymce/ui/Toolbar", [
  28090. "tinymce/ui/Container"
  28091. ], function(Container) {
  28092. "use strict";
  28093. return Container.extend({
  28094. Defaults: {
  28095. role: 'toolbar',
  28096. layout: 'flow'
  28097. },
  28098. /**
  28099. * Constructs a instance with the specified settings.
  28100. *
  28101. * @constructor
  28102. * @param {Object} settings Name/value object with settings.
  28103. */
  28104. init: function(settings) {
  28105. var self = this;
  28106. self._super(settings);
  28107. self.addClass('toolbar');
  28108. },
  28109. /**
  28110. * Called after the control has been rendered.
  28111. *
  28112. * @method postRender
  28113. */
  28114. postRender: function() {
  28115. var self = this;
  28116. self.items().addClass('toolbar-item');
  28117. return self._super();
  28118. }
  28119. });
  28120. });
  28121. // Included from: js/tinymce/classes/ui/MenuBar.js
  28122. /**
  28123. * MenuBar.js
  28124. *
  28125. * Copyright, Moxiecode Systems AB
  28126. * Released under LGPL License.
  28127. *
  28128. * License: http://www.tinymce.com/license
  28129. * Contributing: http://www.tinymce.com/contributing
  28130. */
  28131. /**
  28132. * Creates a new menubar.
  28133. *
  28134. * @-x-less MenuBar.less
  28135. * @class tinymce.ui.MenuBar
  28136. * @extends tinymce.ui.Container
  28137. */
  28138. define("tinymce/ui/MenuBar", [
  28139. "tinymce/ui/Toolbar"
  28140. ], function(Toolbar) {
  28141. "use strict";
  28142. return Toolbar.extend({
  28143. Defaults: {
  28144. role: 'menubar',
  28145. containerCls: 'menubar',
  28146. ariaRoot: true,
  28147. defaults: {
  28148. type: 'menubutton'
  28149. }
  28150. }
  28151. });
  28152. });
  28153. // Included from: js/tinymce/classes/ui/MenuButton.js
  28154. /**
  28155. * MenuButton.js
  28156. *
  28157. * Copyright, Moxiecode Systems AB
  28158. * Released under LGPL License.
  28159. *
  28160. * License: http://www.tinymce.com/license
  28161. * Contributing: http://www.tinymce.com/contributing
  28162. */
  28163. /**
  28164. * Creates a new menu button.
  28165. *
  28166. * @-x-less MenuButton.less
  28167. * @class tinymce.ui.MenuButton
  28168. * @extends tinymce.ui.Button
  28169. */
  28170. define("tinymce/ui/MenuButton", [
  28171. "tinymce/ui/Button",
  28172. "tinymce/ui/Factory",
  28173. "tinymce/ui/MenuBar"
  28174. ], function(Button, Factory, MenuBar) {
  28175. "use strict";
  28176. // TODO: Maybe add as some global function
  28177. function isChildOf(node, parent) {
  28178. while (node) {
  28179. if (parent === node) {
  28180. return true;
  28181. }
  28182. node = node.parentNode;
  28183. }
  28184. return false;
  28185. }
  28186. var MenuButton = Button.extend({
  28187. /**
  28188. * Constructs a instance with the specified settings.
  28189. *
  28190. * @constructor
  28191. * @param {Object} settings Name/value object with settings.
  28192. */
  28193. init: function(settings) {
  28194. var self = this;
  28195. self._renderOpen = true;
  28196. self._super(settings);
  28197. self.addClass('menubtn');
  28198. if (settings.fixedWidth) {
  28199. self.addClass('fixed-width');
  28200. }
  28201. self.aria('haspopup', true);
  28202. self.hasPopup = true;
  28203. },
  28204. /**
  28205. * Shows the menu for the button.
  28206. *
  28207. * @method showMenu
  28208. */
  28209. showMenu: function() {
  28210. var self = this, settings = self.settings, menu;
  28211. if (self.menu && self.menu.visible()) {
  28212. return self.hideMenu();
  28213. }
  28214. if (!self.menu) {
  28215. menu = settings.menu || [];
  28216. // Is menu array then auto constuct menu control
  28217. if (menu.length) {
  28218. menu = {
  28219. type: 'menu',
  28220. items: menu
  28221. };
  28222. } else {
  28223. menu.type = menu.type || 'menu';
  28224. }
  28225. self.menu = Factory.create(menu).parent(self).renderTo();
  28226. self.fire('createmenu');
  28227. self.menu.reflow();
  28228. self.menu.on('cancel', function(e) {
  28229. if (e.control.parent() === self.menu) {
  28230. e.stopPropagation();
  28231. self.focus();
  28232. self.hideMenu();
  28233. }
  28234. });
  28235. // Move focus to button when a menu item is selected/clicked
  28236. self.menu.on('select', function() {
  28237. self.focus();
  28238. });
  28239. self.menu.on('show hide', function(e) {
  28240. if (e.control == self.menu) {
  28241. self.activeMenu(e.type == 'show');
  28242. }
  28243. self.aria('expanded', e.type == 'show');
  28244. }).fire('show');
  28245. }
  28246. self.menu.show();
  28247. self.menu.layoutRect({w: self.layoutRect().w});
  28248. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  28249. },
  28250. /**
  28251. * Hides the menu for the button.
  28252. *
  28253. * @method hideMenu
  28254. */
  28255. hideMenu: function() {
  28256. var self = this;
  28257. if (self.menu) {
  28258. self.menu.items().each(function(item) {
  28259. if (item.hideMenu) {
  28260. item.hideMenu();
  28261. }
  28262. });
  28263. self.menu.hide();
  28264. }
  28265. },
  28266. /**
  28267. * Sets the active menu state.
  28268. *
  28269. * @private
  28270. */
  28271. activeMenu: function(state) {
  28272. this.toggleClass('active', state);
  28273. },
  28274. /**
  28275. * Renders the control as a HTML string.
  28276. *
  28277. * @method renderHtml
  28278. * @return {String} HTML representing the control.
  28279. */
  28280. renderHtml: function() {
  28281. var self = this, id = self._id, prefix = self.classPrefix;
  28282. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  28283. self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
  28284. return (
  28285. '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
  28286. '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
  28287. (icon ? '<i class="' + icon + '"></i>' : '') +
  28288. '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
  28289. ' <i class="' + prefix + 'caret"></i>' +
  28290. '</button>' +
  28291. '</div>'
  28292. );
  28293. },
  28294. /**
  28295. * Gets invoked after the control has been rendered.
  28296. *
  28297. * @method postRender
  28298. */
  28299. postRender: function() {
  28300. var self = this;
  28301. self.on('click', function(e) {
  28302. if (e.control === self && isChildOf(e.target, self.getEl())) {
  28303. self.showMenu();
  28304. if (e.aria) {
  28305. self.menu.items()[0].focus();
  28306. }
  28307. }
  28308. });
  28309. self.on('mouseenter', function(e) {
  28310. var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
  28311. if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
  28312. parent.items().filter('MenuButton').each(function(ctrl) {
  28313. if (ctrl.hideMenu && ctrl != overCtrl) {
  28314. if (ctrl.menu && ctrl.menu.visible()) {
  28315. hasVisibleSiblingMenu = true;
  28316. }
  28317. ctrl.hideMenu();
  28318. }
  28319. });
  28320. if (hasVisibleSiblingMenu) {
  28321. overCtrl.focus(); // Fix for: #5887
  28322. overCtrl.showMenu();
  28323. }
  28324. }
  28325. });
  28326. return self._super();
  28327. },
  28328. /**
  28329. * Sets/gets the current button text.
  28330. *
  28331. * @method text
  28332. * @param {String} [text] New button text.
  28333. * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance.
  28334. */
  28335. text: function(text) {
  28336. var self = this, i, children;
  28337. if (self._rendered) {
  28338. children = self.getEl('open').getElementsByTagName('span');
  28339. for (i = 0; i < children.length; i++) {
  28340. children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
  28341. }
  28342. }
  28343. return this._super(text);
  28344. },
  28345. /**
  28346. * Removes the control and it's menus.
  28347. *
  28348. * @method remove
  28349. */
  28350. remove: function() {
  28351. this._super();
  28352. if (this.menu) {
  28353. this.menu.remove();
  28354. }
  28355. }
  28356. });
  28357. return MenuButton;
  28358. });
  28359. // Included from: js/tinymce/classes/ui/ListBox.js
  28360. /**
  28361. * ListBox.js
  28362. *
  28363. * Copyright, Moxiecode Systems AB
  28364. * Released under LGPL License.
  28365. *
  28366. * License: http://www.tinymce.com/license
  28367. * Contributing: http://www.tinymce.com/contributing
  28368. */
  28369. /**
  28370. * Creates a new list box control.
  28371. *
  28372. * @-x-less ListBox.less
  28373. * @class tinymce.ui.ListBox
  28374. * @extends tinymce.ui.MenuButton
  28375. */
  28376. define("tinymce/ui/ListBox", [
  28377. "tinymce/ui/MenuButton"
  28378. ], function(MenuButton) {
  28379. "use strict";
  28380. return MenuButton.extend({
  28381. /**
  28382. * Constructs a instance with the specified settings.
  28383. *
  28384. * @constructor
  28385. * @param {Object} settings Name/value object with settings.
  28386. * @setting {Array} values Array with values to add to list box.
  28387. */
  28388. init: function(settings) {
  28389. var self = this, values, i, selected, selectedText, lastItemCtrl;
  28390. self._values = values = settings.values;
  28391. if (values) {
  28392. for (i = 0; i < values.length; i++) {
  28393. selected = values[i].selected || settings.value === values[i].value;
  28394. if (selected) {
  28395. selectedText = selectedText || values[i].text;
  28396. self._value = values[i].value;
  28397. break;
  28398. }
  28399. }
  28400. // Default with first item
  28401. if (!selected && values.length > 0) {
  28402. selectedText = values[0].text;
  28403. self._value = values[0].value;
  28404. }
  28405. settings.menu = values;
  28406. }
  28407. settings.text = settings.text || selectedText || values[0].text;
  28408. self._super(settings);
  28409. self.addClass('listbox');
  28410. self.on('select', function(e) {
  28411. var ctrl = e.control;
  28412. if (lastItemCtrl) {
  28413. e.lastControl = lastItemCtrl;
  28414. }
  28415. if (settings.multiple) {
  28416. ctrl.active(!ctrl.active());
  28417. } else {
  28418. self.value(e.control.settings.value);
  28419. }
  28420. lastItemCtrl = ctrl;
  28421. });
  28422. },
  28423. /**
  28424. * Getter/setter function for the control value.
  28425. *
  28426. * @method value
  28427. * @param {String} [value] Value to be set.
  28428. * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
  28429. */
  28430. value: function(value) {
  28431. var self = this, active, selectedText, menu, i;
  28432. function activateByValue(menu, value) {
  28433. menu.items().each(function(ctrl) {
  28434. active = ctrl.value() === value;
  28435. if (active) {
  28436. selectedText = selectedText || ctrl.text();
  28437. }
  28438. ctrl.active(active);
  28439. if (ctrl.menu) {
  28440. activateByValue(ctrl.menu, value);
  28441. }
  28442. });
  28443. }
  28444. if (typeof(value) != "undefined") {
  28445. if (self.menu) {
  28446. activateByValue(self.menu, value);
  28447. } else {
  28448. menu = self.settings.menu;
  28449. for (i = 0; i < menu.length; i++) {
  28450. active = menu[i].value == value;
  28451. if (active) {
  28452. selectedText = selectedText || menu[i].text;
  28453. }
  28454. menu[i].active = active;
  28455. }
  28456. }
  28457. self.text(selectedText || this.settings.text);
  28458. }
  28459. return self._super(value);
  28460. }
  28461. });
  28462. });
  28463. // Included from: js/tinymce/classes/ui/MenuItem.js
  28464. /**
  28465. * MenuItem.js
  28466. *
  28467. * Copyright, Moxiecode Systems AB
  28468. * Released under LGPL License.
  28469. *
  28470. * License: http://www.tinymce.com/license
  28471. * Contributing: http://www.tinymce.com/contributing
  28472. */
  28473. /**
  28474. * Creates a new menu item.
  28475. *
  28476. * @-x-less MenuItem.less
  28477. * @class tinymce.ui.MenuItem
  28478. * @extends tinymce.ui.Control
  28479. */
  28480. define("tinymce/ui/MenuItem", [
  28481. "tinymce/ui/Widget",
  28482. "tinymce/ui/Factory",
  28483. "tinymce/Env"
  28484. ], function(Widget, Factory, Env) {
  28485. "use strict";
  28486. return Widget.extend({
  28487. Defaults: {
  28488. border: 0,
  28489. role: 'menuitem'
  28490. },
  28491. /**
  28492. * Constructs a instance with the specified settings.
  28493. *
  28494. * @constructor
  28495. * @param {Object} settings Name/value object with settings.
  28496. * @setting {Boolean} selectable Selectable menu.
  28497. * @setting {Array} menu Submenu array with items.
  28498. * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
  28499. */
  28500. init: function(settings) {
  28501. var self = this;
  28502. self.hasPopup = true;
  28503. self._super(settings);
  28504. settings = self.settings;
  28505. self.addClass('menu-item');
  28506. if (settings.menu) {
  28507. self.addClass('menu-item-expand');
  28508. }
  28509. if (settings.preview) {
  28510. self.addClass('menu-item-preview');
  28511. }
  28512. if (self._text === '-' || self._text === '|') {
  28513. self.addClass('menu-item-sep');
  28514. self.aria('role', 'separator');
  28515. self._text = '-';
  28516. }
  28517. if (settings.selectable) {
  28518. self.aria('role', 'menuitemcheckbox');
  28519. self.addClass('menu-item-checkbox');
  28520. settings.icon = 'selected';
  28521. }
  28522. if (!settings.preview && !settings.selectable) {
  28523. self.addClass('menu-item-normal');
  28524. }
  28525. self.on('mousedown', function(e) {
  28526. e.preventDefault();
  28527. });
  28528. if (settings.menu && !settings.ariaHideMenu) {
  28529. self.aria('haspopup', true);
  28530. }
  28531. },
  28532. /**
  28533. * Returns true/false if the menuitem has sub menu.
  28534. *
  28535. * @method hasMenus
  28536. * @return {Boolean} True/false state if it has submenu.
  28537. */
  28538. hasMenus: function() {
  28539. return !!this.settings.menu;
  28540. },
  28541. /**
  28542. * Shows the menu for the menu item.
  28543. *
  28544. * @method showMenu
  28545. */
  28546. showMenu: function() {
  28547. var self = this, settings = self.settings, menu, parent = self.parent();
  28548. parent.items().each(function(ctrl) {
  28549. if (ctrl !== self) {
  28550. ctrl.hideMenu();
  28551. }
  28552. });
  28553. if (settings.menu) {
  28554. menu = self.menu;
  28555. if (!menu) {
  28556. menu = settings.menu;
  28557. // Is menu array then auto constuct menu control
  28558. if (menu.length) {
  28559. menu = {
  28560. type: 'menu',
  28561. items: menu
  28562. };
  28563. } else {
  28564. menu.type = menu.type || 'menu';
  28565. }
  28566. if (parent.settings.itemDefaults) {
  28567. menu.itemDefaults = parent.settings.itemDefaults;
  28568. }
  28569. menu = self.menu = Factory.create(menu).parent(self).renderTo();
  28570. menu.reflow();
  28571. menu.fire('show');
  28572. menu.on('cancel', function(e) {
  28573. e.stopPropagation();
  28574. self.focus();
  28575. menu.hide();
  28576. });
  28577. menu.on('hide', function(e) {
  28578. if (e.control === menu) {
  28579. self.removeClass('selected');
  28580. }
  28581. });
  28582. menu.submenu = true;
  28583. } else {
  28584. menu.show();
  28585. }
  28586. menu._parentMenu = parent;
  28587. menu.addClass('menu-sub');
  28588. var rel = menu.testMoveRel(
  28589. self.getEl(),
  28590. self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
  28591. );
  28592. menu.moveRel(self.getEl(), rel);
  28593. menu.rel = rel;
  28594. rel = 'menu-sub-' + rel;
  28595. menu.removeClass(menu._lastRel);
  28596. menu.addClass(rel);
  28597. menu._lastRel = rel;
  28598. self.addClass('selected');
  28599. self.aria('expanded', true);
  28600. }
  28601. },
  28602. /**
  28603. * Hides the menu for the menu item.
  28604. *
  28605. * @method hideMenu
  28606. */
  28607. hideMenu: function() {
  28608. var self = this;
  28609. if (self.menu) {
  28610. self.menu.items().each(function(item) {
  28611. if (item.hideMenu) {
  28612. item.hideMenu();
  28613. }
  28614. });
  28615. self.menu.hide();
  28616. self.aria('expanded', false);
  28617. }
  28618. return self;
  28619. },
  28620. /**
  28621. * Renders the control as a HTML string.
  28622. *
  28623. * @method renderHtml
  28624. * @return {String} HTML representing the control.
  28625. */
  28626. renderHtml: function() {
  28627. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
  28628. var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
  28629. if (icon) {
  28630. self.parent().addClass('menu-has-icons');
  28631. }
  28632. if (settings.image) {
  28633. icon = 'none';
  28634. image = ' style="background-image: url(\'' + settings.image + '\')"';
  28635. }
  28636. if (shortcut && Env.mac) {
  28637. // format shortcut for Mac
  28638. shortcut = shortcut.replace(/ctrl\+alt\+/i, '&#x2325;&#x2318;'); // ctrl+cmd
  28639. shortcut = shortcut.replace(/ctrl\+/i, '&#x2318;'); // ctrl symbol
  28640. shortcut = shortcut.replace(/alt\+/i, '&#x2325;'); // cmd symbol
  28641. shortcut = shortcut.replace(/shift\+/i, '&#x21E7;'); // shift symbol
  28642. }
  28643. icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
  28644. return (
  28645. '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
  28646. (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
  28647. (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
  28648. (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
  28649. (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
  28650. '</div>'
  28651. );
  28652. },
  28653. /**
  28654. * Gets invoked after the control has been rendered.
  28655. *
  28656. * @method postRender
  28657. */
  28658. postRender: function() {
  28659. var self = this, settings = self.settings;
  28660. var textStyle = settings.textStyle;
  28661. if (typeof(textStyle) == "function") {
  28662. textStyle = textStyle.call(this);
  28663. }
  28664. if (textStyle) {
  28665. var textElm = self.getEl('text');
  28666. if (textElm) {
  28667. textElm.setAttribute('style', textStyle);
  28668. }
  28669. }
  28670. self.on('mouseenter click', function(e) {
  28671. if (e.control === self) {
  28672. if (!settings.menu && e.type === 'click') {
  28673. self.fire('select');
  28674. self.parent().hideAll();
  28675. } else {
  28676. self.showMenu();
  28677. if (e.aria) {
  28678. self.menu.focus(true);
  28679. }
  28680. }
  28681. }
  28682. });
  28683. self._super();
  28684. return self;
  28685. },
  28686. active: function(state) {
  28687. if (typeof(state) != "undefined") {
  28688. this.aria('checked', state);
  28689. }
  28690. return this._super(state);
  28691. },
  28692. /**
  28693. * Removes the control and it's menus.
  28694. *
  28695. * @method remove
  28696. */
  28697. remove: function() {
  28698. this._super();
  28699. if (this.menu) {
  28700. this.menu.remove();
  28701. }
  28702. }
  28703. });
  28704. });
  28705. // Included from: js/tinymce/classes/ui/Menu.js
  28706. /**
  28707. * Menu.js
  28708. *
  28709. * Copyright, Moxiecode Systems AB
  28710. * Released under LGPL License.
  28711. *
  28712. * License: http://www.tinymce.com/license
  28713. * Contributing: http://www.tinymce.com/contributing
  28714. */
  28715. /**
  28716. * Creates a new menu.
  28717. *
  28718. * @-x-less Menu.less
  28719. * @class tinymce.ui.Menu
  28720. * @extends tinymce.ui.FloatPanel
  28721. */
  28722. define("tinymce/ui/Menu", [
  28723. "tinymce/ui/FloatPanel",
  28724. "tinymce/ui/MenuItem",
  28725. "tinymce/util/Tools"
  28726. ], function(FloatPanel, MenuItem, Tools) {
  28727. "use strict";
  28728. var Menu = FloatPanel.extend({
  28729. Defaults: {
  28730. defaultType: 'menuitem',
  28731. border: 1,
  28732. layout: 'stack',
  28733. role: 'application',
  28734. bodyRole: 'menu',
  28735. ariaRoot: true
  28736. },
  28737. /**
  28738. * Constructs a instance with the specified settings.
  28739. *
  28740. * @constructor
  28741. * @param {Object} settings Name/value object with settings.
  28742. */
  28743. init: function(settings) {
  28744. var self = this;
  28745. settings.autohide = true;
  28746. settings.constrainToViewport = true;
  28747. if (settings.itemDefaults) {
  28748. var items = settings.items, i = items.length;
  28749. while (i--) {
  28750. items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
  28751. }
  28752. }
  28753. self._super(settings);
  28754. self.addClass('menu');
  28755. },
  28756. /**
  28757. * Repaints the control after a layout operation.
  28758. *
  28759. * @method repaint
  28760. */
  28761. repaint: function() {
  28762. this.toggleClass('menu-align', true);
  28763. this._super();
  28764. this.getEl().style.height = '';
  28765. this.getEl('body').style.height = '';
  28766. return this;
  28767. },
  28768. /**
  28769. * Hides/closes the menu.
  28770. *
  28771. * @method cancel
  28772. */
  28773. cancel: function() {
  28774. var self = this;
  28775. self.hideAll();
  28776. self.fire('select');
  28777. },
  28778. /**
  28779. * Hide menu and all sub menus.
  28780. *
  28781. * @method hideAll
  28782. */
  28783. hideAll: function() {
  28784. var self = this;
  28785. this.find('menuitem').exec('hideMenu');
  28786. return self._super();
  28787. },
  28788. /*
  28789. getContainerElm: function() {
  28790. var doc = document, id = this.classPrefix + 'menucontainer';
  28791. var elm = doc.getElementById(id);
  28792. if (!elm) {
  28793. elm = doc.createElement('div');
  28794. elm.id = id;
  28795. elm.setAttribute('role', 'application');
  28796. elm.className = this.classPrefix + '-reset';
  28797. elm.style.position = 'absolute';
  28798. elm.style.top = elm.style.left = '0';
  28799. elm.style.overflow = 'visible';
  28800. doc.body.appendChild(elm);
  28801. }
  28802. return elm;
  28803. },
  28804. */
  28805. /**
  28806. * Invoked before the menu is rendered.
  28807. *
  28808. * @method preRender
  28809. */
  28810. preRender: function() {
  28811. var self = this;
  28812. self.items().each(function(ctrl) {
  28813. var settings = ctrl.settings;
  28814. if (settings.icon || settings.selectable) {
  28815. self._hasIcons = true;
  28816. return false;
  28817. }
  28818. });
  28819. return self._super();
  28820. }
  28821. });
  28822. return Menu;
  28823. });
  28824. // Included from: js/tinymce/classes/ui/Radio.js
  28825. /**
  28826. * Radio.js
  28827. *
  28828. * Copyright, Moxiecode Systems AB
  28829. * Released under LGPL License.
  28830. *
  28831. * License: http://www.tinymce.com/license
  28832. * Contributing: http://www.tinymce.com/contributing
  28833. */
  28834. /**
  28835. * Creates a new radio button.
  28836. *
  28837. * @-x-less Radio.less
  28838. * @class tinymce.ui.Radio
  28839. * @extends tinymce.ui.Checkbox
  28840. */
  28841. define("tinymce/ui/Radio", [
  28842. "tinymce/ui/Checkbox"
  28843. ], function(Checkbox) {
  28844. "use strict";
  28845. return Checkbox.extend({
  28846. Defaults: {
  28847. classes: "radio",
  28848. role: "radio"
  28849. }
  28850. });
  28851. });
  28852. // Included from: js/tinymce/classes/ui/ResizeHandle.js
  28853. /**
  28854. * ResizeHandle.js
  28855. *
  28856. * Copyright, Moxiecode Systems AB
  28857. * Released under LGPL License.
  28858. *
  28859. * License: http://www.tinymce.com/license
  28860. * Contributing: http://www.tinymce.com/contributing
  28861. */
  28862. /**
  28863. * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
  28864. *
  28865. * @-x-less ResizeHandle.less
  28866. * @class tinymce.ui.ResizeHandle
  28867. * @extends tinymce.ui.Widget
  28868. */
  28869. define("tinymce/ui/ResizeHandle", [
  28870. "tinymce/ui/Widget",
  28871. "tinymce/ui/DragHelper"
  28872. ], function(Widget, DragHelper) {
  28873. "use strict";
  28874. return Widget.extend({
  28875. /**
  28876. * Renders the control as a HTML string.
  28877. *
  28878. * @method renderHtml
  28879. * @return {String} HTML representing the control.
  28880. */
  28881. renderHtml: function() {
  28882. var self = this, prefix = self.classPrefix;
  28883. self.addClass('resizehandle');
  28884. if (self.settings.direction == "both") {
  28885. self.addClass('resizehandle-both');
  28886. }
  28887. self.canFocus = false;
  28888. return (
  28889. '<div id="' + self._id + '" class="' + self.classes() + '">' +
  28890. '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
  28891. '</div>'
  28892. );
  28893. },
  28894. /**
  28895. * Called after the control has been rendered.
  28896. *
  28897. * @method postRender
  28898. */
  28899. postRender: function() {
  28900. var self = this;
  28901. self._super();
  28902. self.resizeDragHelper = new DragHelper(this._id, {
  28903. start: function() {
  28904. self.fire('ResizeStart');
  28905. },
  28906. drag: function(e) {
  28907. if (self.settings.direction != "both") {
  28908. e.deltaX = 0;
  28909. }
  28910. self.fire('Resize', e);
  28911. },
  28912. stop: function() {
  28913. self.fire('ResizeEnd');
  28914. }
  28915. });
  28916. },
  28917. remove: function() {
  28918. if (this.resizeDragHelper) {
  28919. this.resizeDragHelper.destroy();
  28920. }
  28921. return this._super();
  28922. }
  28923. });
  28924. });
  28925. // Included from: js/tinymce/classes/ui/Spacer.js
  28926. /**
  28927. * Spacer.js
  28928. *
  28929. * Copyright, Moxiecode Systems AB
  28930. * Released under LGPL License.
  28931. *
  28932. * License: http://www.tinymce.com/license
  28933. * Contributing: http://www.tinymce.com/contributing
  28934. */
  28935. /**
  28936. * Creates a spacer. This control is used in flex layouts for example.
  28937. *
  28938. * @-x-less Spacer.less
  28939. * @class tinymce.ui.Spacer
  28940. * @extends tinymce.ui.Widget
  28941. */
  28942. define("tinymce/ui/Spacer", [
  28943. "tinymce/ui/Widget"
  28944. ], function(Widget) {
  28945. "use strict";
  28946. return Widget.extend({
  28947. /**
  28948. * Renders the control as a HTML string.
  28949. *
  28950. * @method renderHtml
  28951. * @return {String} HTML representing the control.
  28952. */
  28953. renderHtml: function() {
  28954. var self = this;
  28955. self.addClass('spacer');
  28956. self.canFocus = false;
  28957. return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
  28958. }
  28959. });
  28960. });
  28961. // Included from: js/tinymce/classes/ui/SplitButton.js
  28962. /**
  28963. * SplitButton.js
  28964. *
  28965. * Copyright, Moxiecode Systems AB
  28966. * Released under LGPL License.
  28967. *
  28968. * License: http://www.tinymce.com/license
  28969. * Contributing: http://www.tinymce.com/contributing
  28970. */
  28971. /**
  28972. * Creates a split button.
  28973. *
  28974. * @-x-less SplitButton.less
  28975. * @class tinymce.ui.SplitButton
  28976. * @extends tinymce.ui.Button
  28977. */
  28978. define("tinymce/ui/SplitButton", [
  28979. "tinymce/ui/MenuButton",
  28980. "tinymce/ui/DomUtils"
  28981. ], function(MenuButton, DomUtils) {
  28982. return MenuButton.extend({
  28983. Defaults: {
  28984. classes: "widget btn splitbtn",
  28985. role: "button"
  28986. },
  28987. /**
  28988. * Repaints the control after a layout operation.
  28989. *
  28990. * @method repaint
  28991. */
  28992. repaint: function() {
  28993. var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
  28994. self._super();
  28995. mainButtonElm = elm.firstChild;
  28996. menuButtonElm = elm.lastChild;
  28997. DomUtils.css(mainButtonElm, {
  28998. width: rect.w - DomUtils.getSize(menuButtonElm).width,
  28999. height: rect.h - 2
  29000. });
  29001. DomUtils.css(menuButtonElm, {
  29002. height: rect.h - 2
  29003. });
  29004. return self;
  29005. },
  29006. /**
  29007. * Sets the active menu state.
  29008. *
  29009. * @private
  29010. */
  29011. activeMenu: function(state) {
  29012. var self = this;
  29013. DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
  29014. },
  29015. /**
  29016. * Renders the control as a HTML string.
  29017. *
  29018. * @method renderHtml
  29019. * @return {String} HTML representing the control.
  29020. */
  29021. renderHtml: function() {
  29022. var self = this, id = self._id, prefix = self.classPrefix;
  29023. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  29024. return (
  29025. '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
  29026. '<button type="button" hidefocus="1" tabindex="-1">' +
  29027. (icon ? '<i class="' + icon + '"></i>' : '') +
  29028. (self._text ? (icon ? ' ' : '') + self._text : '') +
  29029. '</button>' +
  29030. '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  29031. //(icon ? '<i class="' + icon + '"></i>' : '') +
  29032. (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
  29033. ' <i class="' + prefix + 'caret"></i>' +
  29034. '</button>' +
  29035. '</div>'
  29036. );
  29037. },
  29038. /**
  29039. * Called after the control has been rendered.
  29040. *
  29041. * @method postRender
  29042. */
  29043. postRender: function() {
  29044. var self = this, onClickHandler = self.settings.onclick;
  29045. self.on('click', function(e) {
  29046. var node = e.target;
  29047. if (e.control == this) {
  29048. // Find clicks that is on the main button
  29049. while (node) {
  29050. if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
  29051. e.stopImmediatePropagation();
  29052. onClickHandler.call(this, e);
  29053. return;
  29054. }
  29055. node = node.parentNode;
  29056. }
  29057. }
  29058. });
  29059. delete self.settings.onclick;
  29060. return self._super();
  29061. }
  29062. });
  29063. });
  29064. // Included from: js/tinymce/classes/ui/StackLayout.js
  29065. /**
  29066. * StackLayout.js
  29067. *
  29068. * Copyright, Moxiecode Systems AB
  29069. * Released under LGPL License.
  29070. *
  29071. * License: http://www.tinymce.com/license
  29072. * Contributing: http://www.tinymce.com/contributing
  29073. */
  29074. /**
  29075. * This layout uses the browsers layout when the items are blocks.
  29076. *
  29077. * @-x-less StackLayout.less
  29078. * @class tinymce.ui.StackLayout
  29079. * @extends tinymce.ui.FlowLayout
  29080. */
  29081. define("tinymce/ui/StackLayout", [
  29082. "tinymce/ui/FlowLayout"
  29083. ], function(FlowLayout) {
  29084. "use strict";
  29085. return FlowLayout.extend({
  29086. Defaults: {
  29087. containerClass: 'stack-layout',
  29088. controlClass: 'stack-layout-item',
  29089. endClass : 'break'
  29090. }
  29091. });
  29092. });
  29093. // Included from: js/tinymce/classes/ui/TabPanel.js
  29094. /**
  29095. * TabPanel.js
  29096. *
  29097. * Copyright, Moxiecode Systems AB
  29098. * Released under LGPL License.
  29099. *
  29100. * License: http://www.tinymce.com/license
  29101. * Contributing: http://www.tinymce.com/contributing
  29102. */
  29103. /**
  29104. * Creates a tab panel control.
  29105. *
  29106. * @-x-less TabPanel.less
  29107. * @class tinymce.ui.TabPanel
  29108. * @extends tinymce.ui.Panel
  29109. *
  29110. * @setting {Number} activeTab Active tab index.
  29111. */
  29112. define("tinymce/ui/TabPanel", [
  29113. "tinymce/ui/Panel",
  29114. "tinymce/ui/DomUtils"
  29115. ], function(Panel, DomUtils) {
  29116. "use strict";
  29117. return Panel.extend({
  29118. lastIdx: 0,
  29119. Defaults: {
  29120. layout: 'absolute',
  29121. defaults: {
  29122. type: 'panel'
  29123. }
  29124. },
  29125. /**
  29126. * Activates the specified tab by index.
  29127. *
  29128. * @method activateTab
  29129. * @param {Number} idx Index of the tab to activate.
  29130. */
  29131. activateTab: function(idx) {
  29132. var activeTabElm;
  29133. if (this.activeTabId) {
  29134. activeTabElm = this.getEl(this.activeTabId);
  29135. DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
  29136. activeTabElm.setAttribute('aria-selected', "false");
  29137. }
  29138. this.activeTabId = 't' + idx;
  29139. activeTabElm = this.getEl('t' + idx);
  29140. activeTabElm.setAttribute('aria-selected', "true");
  29141. DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
  29142. if (idx != this.lastIdx) {
  29143. this.items()[this.lastIdx].hide();
  29144. this.lastIdx = idx;
  29145. }
  29146. this.items()[idx].show().fire('showtab');
  29147. this.reflow();
  29148. },
  29149. /**
  29150. * Renders the control as a HTML string.
  29151. *
  29152. * @method renderHtml
  29153. * @return {String} HTML representing the control.
  29154. */
  29155. renderHtml: function() {
  29156. var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
  29157. self.preRender();
  29158. layout.preRender(self);
  29159. self.items().each(function(ctrl, i) {
  29160. var id = self._id + '-t' + i;
  29161. ctrl.aria('role', 'tabpanel');
  29162. ctrl.aria('labelledby', id);
  29163. tabsHtml += (
  29164. '<div id="' + id + '" class="' + prefix + 'tab" ' +
  29165. 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
  29166. self.encode(ctrl.settings.title) +
  29167. '</div>'
  29168. );
  29169. });
  29170. return (
  29171. '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  29172. '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
  29173. tabsHtml +
  29174. '</div>' +
  29175. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  29176. layout.renderHtml(self) +
  29177. '</div>' +
  29178. '</div>'
  29179. );
  29180. },
  29181. /**
  29182. * Called after the control has been rendered.
  29183. *
  29184. * @method postRender
  29185. */
  29186. postRender: function() {
  29187. var self = this;
  29188. self._super();
  29189. self.settings.activeTab = self.settings.activeTab || 0;
  29190. self.activateTab(self.settings.activeTab);
  29191. this.on('click', function(e) {
  29192. var targetParent = e.target.parentNode;
  29193. if (e.target.parentNode.id == self._id + '-head') {
  29194. var i = targetParent.childNodes.length;
  29195. while (i--) {
  29196. if (targetParent.childNodes[i] == e.target) {
  29197. self.activateTab(i);
  29198. }
  29199. }
  29200. }
  29201. });
  29202. },
  29203. /**
  29204. * Initializes the current controls layout rect.
  29205. * This will be executed by the layout managers to determine the
  29206. * default minWidth/minHeight etc.
  29207. *
  29208. * @method initLayoutRect
  29209. * @return {Object} Layout rect instance.
  29210. */
  29211. initLayoutRect: function() {
  29212. var self = this, rect, minW, minH;
  29213. minW = DomUtils.getSize(self.getEl('head')).width;
  29214. minW = minW < 0 ? 0 : minW;
  29215. minH = 0;
  29216. self.items().each(function(item, i) {
  29217. minW = Math.max(minW, item.layoutRect().minW);
  29218. minH = Math.max(minH, item.layoutRect().minH);
  29219. if (self.settings.activeTab != i) {
  29220. item.hide();
  29221. }
  29222. });
  29223. self.items().each(function(ctrl) {
  29224. ctrl.settings.x = 0;
  29225. ctrl.settings.y = 0;
  29226. ctrl.settings.w = minW;
  29227. ctrl.settings.h = minH;
  29228. ctrl.layoutRect({
  29229. x: 0,
  29230. y: 0,
  29231. w: minW,
  29232. h: minH
  29233. });
  29234. });
  29235. var headH = DomUtils.getSize(self.getEl('head')).height;
  29236. self.settings.minWidth = minW;
  29237. self.settings.minHeight = minH + headH;
  29238. rect = self._super();
  29239. rect.deltaH += headH;
  29240. rect.innerH = rect.h - rect.deltaH;
  29241. return rect;
  29242. }
  29243. });
  29244. });
  29245. // Included from: js/tinymce/classes/ui/TextBox.js
  29246. /**
  29247. * TextBox.js
  29248. *
  29249. * Copyright, Moxiecode Systems AB
  29250. * Released under LGPL License.
  29251. *
  29252. * License: http://www.tinymce.com/license
  29253. * Contributing: http://www.tinymce.com/contributing
  29254. */
  29255. /**
  29256. * Creates a new textbox.
  29257. *
  29258. * @-x-less TextBox.less
  29259. * @class tinymce.ui.TextBox
  29260. * @extends tinymce.ui.Widget
  29261. */
  29262. define("tinymce/ui/TextBox", [
  29263. "tinymce/ui/Widget",
  29264. "tinymce/ui/DomUtils"
  29265. ], function(Widget, DomUtils) {
  29266. "use strict";
  29267. return Widget.extend({
  29268. /**
  29269. * Constructs a instance with the specified settings.
  29270. *
  29271. * @constructor
  29272. * @param {Object} settings Name/value object with settings.
  29273. * @setting {Boolean} multiline True if the textbox is a multiline control.
  29274. * @setting {Number} maxLength Max length for the textbox.
  29275. * @setting {Number} size Size of the textbox in characters.
  29276. */
  29277. init: function(settings) {
  29278. var self = this;
  29279. self._super(settings);
  29280. self._value = settings.value || '';
  29281. self.addClass('textbox');
  29282. if (settings.multiline) {
  29283. self.addClass('multiline');
  29284. } else {
  29285. // TODO: Rework this
  29286. self.on('keydown', function(e) {
  29287. if (e.keyCode == 13) {
  29288. self.parents().reverse().each(function(ctrl) {
  29289. e.preventDefault();
  29290. if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  29291. ctrl.fire('submit', {data: ctrl.toJSON()});
  29292. return false;
  29293. }
  29294. });
  29295. }
  29296. });
  29297. }
  29298. },
  29299. /**
  29300. * Getter/setter function for the disabled state.
  29301. *
  29302. * @method value
  29303. * @param {Boolean} [state] State to be set.
  29304. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
  29305. */
  29306. disabled: function(state) {
  29307. var self = this;
  29308. if (self._rendered && typeof(state) != 'undefined') {
  29309. self.getEl().disabled = state;
  29310. }
  29311. return self._super(state);
  29312. },
  29313. /**
  29314. * Getter/setter function for the control value.
  29315. *
  29316. * @method value
  29317. * @param {String} [value] Value to be set.
  29318. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
  29319. */
  29320. value: function(value) {
  29321. var self = this;
  29322. if (typeof(value) != "undefined") {
  29323. self._value = value;
  29324. if (self._rendered) {
  29325. self.getEl().value = value;
  29326. }
  29327. return self;
  29328. }
  29329. if (self._rendered) {
  29330. return self.getEl().value;
  29331. }
  29332. return self._value;
  29333. },
  29334. /**
  29335. * Repaints the control after a layout operation.
  29336. *
  29337. * @method repaint
  29338. */
  29339. repaint: function() {
  29340. var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
  29341. style = self.getEl().style;
  29342. rect = self._layoutRect;
  29343. lastRepaintRect = self._lastRepaintRect || {};
  29344. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  29345. var doc = document;
  29346. if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  29347. style.lineHeight = (rect.h - borderH) + 'px';
  29348. }
  29349. borderBox = self._borderBox;
  29350. borderW = borderBox.left + borderBox.right + 8;
  29351. borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
  29352. if (rect.x !== lastRepaintRect.x) {
  29353. style.left = rect.x + 'px';
  29354. lastRepaintRect.x = rect.x;
  29355. }
  29356. if (rect.y !== lastRepaintRect.y) {
  29357. style.top = rect.y + 'px';
  29358. lastRepaintRect.y = rect.y;
  29359. }
  29360. if (rect.w !== lastRepaintRect.w) {
  29361. style.width = (rect.w - borderW) + 'px';
  29362. lastRepaintRect.w = rect.w;
  29363. }
  29364. if (rect.h !== lastRepaintRect.h) {
  29365. style.height = (rect.h - borderH) + 'px';
  29366. lastRepaintRect.h = rect.h;
  29367. }
  29368. self._lastRepaintRect = lastRepaintRect;
  29369. self.fire('repaint', {}, false);
  29370. return self;
  29371. },
  29372. /**
  29373. * Renders the control as a HTML string.
  29374. *
  29375. * @method renderHtml
  29376. * @return {String} HTML representing the control.
  29377. */
  29378. renderHtml: function() {
  29379. var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';
  29380. if ("spellcheck" in settings) {
  29381. extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  29382. }
  29383. if (settings.maxLength) {
  29384. extraAttrs += ' maxlength="' + settings.maxLength + '"';
  29385. }
  29386. if (settings.size) {
  29387. extraAttrs += ' size="' + settings.size + '"';
  29388. }
  29389. if (settings.subtype) {
  29390. extraAttrs += ' type="' + settings.subtype + '"';
  29391. }
  29392. if (self.disabled()) {
  29393. extraAttrs += ' disabled="disabled"';
  29394. }
  29395. if (settings.multiline) {
  29396. return (
  29397. '<textarea id="' + id + '" class="' + self.classes() + '" ' +
  29398. (settings.rows ? ' rows="' + settings.rows + '"' : '') +
  29399. ' hidefocus="1"' + extraAttrs + '>' + value +
  29400. '</textarea>'
  29401. );
  29402. }
  29403. return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
  29404. },
  29405. /**
  29406. * Called after the control has been rendered.
  29407. *
  29408. * @method postRender
  29409. */
  29410. postRender: function() {
  29411. var self = this;
  29412. DomUtils.on(self.getEl(), 'change', function(e) {
  29413. self.fire('change', e);
  29414. });
  29415. return self._super();
  29416. },
  29417. remove: function() {
  29418. DomUtils.off(this.getEl());
  29419. this._super();
  29420. }
  29421. });
  29422. });
  29423. // Included from: js/tinymce/classes/ui/Throbber.js
  29424. /**
  29425. * Throbber.js
  29426. *
  29427. * Copyright, Moxiecode Systems AB
  29428. * Released under LGPL License.
  29429. *
  29430. * License: http://www.tinymce.com/license
  29431. * Contributing: http://www.tinymce.com/contributing
  29432. */
  29433. /**
  29434. * This class enables you to display a Throbber for any element.
  29435. *
  29436. * @-x-less Throbber.less
  29437. * @class tinymce.ui.Throbber
  29438. */
  29439. define("tinymce/ui/Throbber", [
  29440. "tinymce/ui/DomUtils",
  29441. "tinymce/ui/Control"
  29442. ], function(DomUtils, Control) {
  29443. "use strict";
  29444. /**
  29445. * Constructs a new throbber.
  29446. *
  29447. * @constructor
  29448. * @param {Element} elm DOM Html element to display throbber in.
  29449. * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
  29450. */
  29451. return function(elm, inline) {
  29452. var self = this, state, classPrefix = Control.classPrefix;
  29453. /**
  29454. * Shows the throbber.
  29455. *
  29456. * @method show
  29457. * @param {Number} [time] Time to wait before showing.
  29458. * @return {tinymce.ui.Throbber} Current throbber instance.
  29459. */
  29460. self.show = function(time) {
  29461. self.hide();
  29462. state = true;
  29463. window.setTimeout(function() {
  29464. if (state) {
  29465. elm.appendChild(DomUtils.createFragment(
  29466. '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
  29467. ));
  29468. }
  29469. }, time || 0);
  29470. return self;
  29471. };
  29472. /**
  29473. * Hides the throbber.
  29474. *
  29475. * @method hide
  29476. * @return {tinymce.ui.Throbber} Current throbber instance.
  29477. */
  29478. self.hide = function() {
  29479. var child = elm.lastChild;
  29480. if (child && child.className.indexOf('throbber') != -1) {
  29481. child.parentNode.removeChild(child);
  29482. }
  29483. state = false;
  29484. return self;
  29485. };
  29486. };
  29487. });
  29488. expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/RangeUtils","tinymce/dom/Selection","tinymce/fmt/Preview","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","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/util/Quirks","tinymce/util/Observable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","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/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","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/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
  29489. })(this);