tinymce.full.js 913 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947
  1. // 4.0.21 (2014-03-xx)
  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 && /(java|vb)script:/i.test(url.replace(/[\s\r\n]+/, ''))) {
  2939. return "";
  2940. }
  2941. // Convert the URL to relative/absolute depending on config
  2942. if (urlConverter) {
  2943. url = urlConverter.call(urlConverterScope, url, 'style');
  2944. }
  2945. // Output new URL format
  2946. return "url('" + url.replace(/\'/g, "\\'") + "')";
  2947. }
  2948. if (css) {
  2949. css = css.replace(/[\u0000-\u001F]/g, '');
  2950. // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
  2951. css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
  2952. return str.replace(/[;:]/g, encode);
  2953. });
  2954. // Parse styles
  2955. while ((matches = styleRegExp.exec(css))) {
  2956. name = matches[1].replace(trimRightRegExp, '').toLowerCase();
  2957. value = matches[2].replace(trimRightRegExp, '');
  2958. if (name && value.length > 0) {
  2959. if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(/.test(value))) {
  2960. continue;
  2961. }
  2962. // Opera will produce 700 instead of bold in their style values
  2963. if (name === 'font-weight' && value === '700') {
  2964. value = 'bold';
  2965. } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
  2966. value = value.toLowerCase();
  2967. }
  2968. // Convert RGB colors to HEX
  2969. value = value.replace(rgbRegExp, toHex);
  2970. // Convert URLs and force them into url('value') format
  2971. value = value.replace(urlOrStrRegExp, processUrl);
  2972. styles[name] = isEncoded ? decode(value, true) : value;
  2973. }
  2974. styleRegExp.lastIndex = matches.index + matches[0].length;
  2975. }
  2976. // Compress the styles to reduce it's size for example IE will expand styles
  2977. compress("border", "", true);
  2978. compress("border", "-width");
  2979. compress("border", "-color");
  2980. compress("border", "-style");
  2981. compress("padding", "");
  2982. compress("margin", "");
  2983. compress2('border', 'border-width', 'border-style', 'border-color');
  2984. // Remove pointless border, IE produces these
  2985. if (styles.border === 'medium none') {
  2986. delete styles.border;
  2987. }
  2988. // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
  2989. // So lets asume it shouldn't be there
  2990. if (styles['border-image'] === 'none') {
  2991. delete styles['border-image'];
  2992. }
  2993. }
  2994. return styles;
  2995. },
  2996. /**
  2997. * Serializes the specified style object into a string.
  2998. *
  2999. * @method serialize
  3000. * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
  3001. * @param {String} element_name Optional element name, if specified only the styles that matches the schema will be serialized.
  3002. * @return {String} String representation of the style object for example: border: 1px solid red.
  3003. */
  3004. serialize: function(styles, element_name) {
  3005. var css = '', name, value;
  3006. function serializeStyles(name) {
  3007. var styleList, i, l, value;
  3008. styleList = schema.styles[name];
  3009. if (styleList) {
  3010. for (i = 0, l = styleList.length; i < l; i++) {
  3011. name = styleList[i];
  3012. value = styles[name];
  3013. if (value !== undef && value.length > 0) {
  3014. css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  3015. }
  3016. }
  3017. }
  3018. }
  3019. // Serialize styles according to schema
  3020. if (element_name && schema && schema.styles) {
  3021. // Serialize global styles and element specific styles
  3022. serializeStyles('*');
  3023. serializeStyles(element_name);
  3024. } else {
  3025. // Output the styles in the order they are inside the object
  3026. for (name in styles) {
  3027. value = styles[name];
  3028. if (value !== undef && value.length > 0) {
  3029. css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
  3030. }
  3031. }
  3032. }
  3033. return css;
  3034. }
  3035. };
  3036. };
  3037. });
  3038. // Included from: js/tinymce/classes/dom/TreeWalker.js
  3039. /**
  3040. * TreeWalker.js
  3041. *
  3042. * Copyright, Moxiecode Systems AB
  3043. * Released under LGPL License.
  3044. *
  3045. * License: http://www.tinymce.com/license
  3046. * Contributing: http://www.tinymce.com/contributing
  3047. */
  3048. /**
  3049. * TreeWalker class enables you to walk the DOM in a linear manner.
  3050. *
  3051. * @class tinymce.dom.TreeWalker
  3052. */
  3053. define("tinymce/dom/TreeWalker", [], function() {
  3054. return function(start_node, root_node) {
  3055. var node = start_node;
  3056. function findSibling(node, start_name, sibling_name, shallow) {
  3057. var sibling, parent;
  3058. if (node) {
  3059. // Walk into nodes if it has a start
  3060. if (!shallow && node[start_name]) {
  3061. return node[start_name];
  3062. }
  3063. // Return the sibling if it has one
  3064. if (node != root_node) {
  3065. sibling = node[sibling_name];
  3066. if (sibling) {
  3067. return sibling;
  3068. }
  3069. // Walk up the parents to look for siblings
  3070. for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
  3071. sibling = parent[sibling_name];
  3072. if (sibling) {
  3073. return sibling;
  3074. }
  3075. }
  3076. }
  3077. }
  3078. }
  3079. /**
  3080. * Returns the current node.
  3081. *
  3082. * @method current
  3083. * @return {Node} Current node where the walker is.
  3084. */
  3085. this.current = function() {
  3086. return node;
  3087. };
  3088. /**
  3089. * Walks to the next node in tree.
  3090. *
  3091. * @method next
  3092. * @return {Node} Current node where the walker is after moving to the next node.
  3093. */
  3094. this.next = function(shallow) {
  3095. node = findSibling(node, 'firstChild', 'nextSibling', shallow);
  3096. return node;
  3097. };
  3098. /**
  3099. * Walks to the previous node in tree.
  3100. *
  3101. * @method prev
  3102. * @return {Node} Current node where the walker is after moving to the previous node.
  3103. */
  3104. this.prev = function(shallow) {
  3105. node = findSibling(node, 'lastChild', 'previousSibling', shallow);
  3106. return node;
  3107. };
  3108. };
  3109. });
  3110. // Included from: js/tinymce/classes/util/Tools.js
  3111. /**
  3112. * Tools.js
  3113. *
  3114. * Copyright, Moxiecode Systems AB
  3115. * Released under LGPL License.
  3116. *
  3117. * License: http://www.tinymce.com/license
  3118. * Contributing: http://www.tinymce.com/contributing
  3119. */
  3120. /**
  3121. * This class contains various utlity functions. These are also exposed
  3122. * directly on the tinymce namespace.
  3123. *
  3124. * @class tinymce.util.Tools
  3125. */
  3126. define("tinymce/util/Tools", [], function() {
  3127. /**
  3128. * Removes whitespace from the beginning and end of a string.
  3129. *
  3130. * @method trim
  3131. * @param {String} s String to remove whitespace from.
  3132. * @return {String} New string with removed whitespace.
  3133. */
  3134. var whiteSpaceRegExp = /^\s*|\s*$/g;
  3135. function trim(str) {
  3136. return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
  3137. }
  3138. /**
  3139. * Returns true/false if the object is an array or not.
  3140. *
  3141. * @method isArray
  3142. * @param {Object} obj Object to check.
  3143. * @return {boolean} true/false state if the object is an array or not.
  3144. */
  3145. var isArray = Array.isArray || function(obj) {
  3146. return Object.prototype.toString.call(obj) === "[object Array]";
  3147. };
  3148. /**
  3149. * Checks if a object is of a specific type for example an array.
  3150. *
  3151. * @method is
  3152. * @param {Object} o Object to check type of.
  3153. * @param {string} t Optional type to check for.
  3154. * @return {Boolean} true/false if the object is of the specified type.
  3155. */
  3156. function is(o, t) {
  3157. if (!t) {
  3158. return o !== undefined;
  3159. }
  3160. if (t == 'array' && isArray(o)) {
  3161. return true;
  3162. }
  3163. return typeof(o) == t;
  3164. }
  3165. /**
  3166. * Converts the specified object into a real JavaScript array.
  3167. *
  3168. * @method toArray
  3169. * @param {Object} obj Object to convert into array.
  3170. * @return {Array} Array object based in input.
  3171. */
  3172. function toArray(obj) {
  3173. var array = [], i, l;
  3174. for (i = 0, l = obj.length; i < l; i++) {
  3175. array[i] = obj[i];
  3176. }
  3177. return array;
  3178. }
  3179. /**
  3180. * Makes a name/object map out of an array with names.
  3181. *
  3182. * @method makeMap
  3183. * @param {Array/String} items Items to make map out of.
  3184. * @param {String} delim Optional delimiter to split string by.
  3185. * @param {Object} map Optional map to add items to.
  3186. * @return {Object} Name/value map of items.
  3187. */
  3188. function makeMap(items, delim, map) {
  3189. var i;
  3190. items = items || [];
  3191. delim = delim || ',';
  3192. if (typeof(items) == "string") {
  3193. items = items.split(delim);
  3194. }
  3195. map = map || {};
  3196. i = items.length;
  3197. while (i--) {
  3198. map[items[i]] = {};
  3199. }
  3200. return map;
  3201. }
  3202. /**
  3203. * Performs an iteration of all items in a collection such as an object or array. This method will execure the
  3204. * callback function for each item in the collection, if the callback returns false the iteration will terminate.
  3205. * The callback has the following format: cb(value, key_or_index).
  3206. *
  3207. * @method each
  3208. * @param {Object} o Collection to iterate.
  3209. * @param {function} cb Callback function to execute for each item.
  3210. * @param {Object} s Optional scope to execute the callback in.
  3211. * @example
  3212. * // Iterate an array
  3213. * tinymce.each([1,2,3], function(v, i) {
  3214. * console.debug("Value: " + v + ", Index: " + i);
  3215. * });
  3216. *
  3217. * // Iterate an object
  3218. * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
  3219. * console.debug("Value: " + v + ", Key: " + k);
  3220. * });
  3221. */
  3222. function each(o, cb, s) {
  3223. var n, l;
  3224. if (!o) {
  3225. return 0;
  3226. }
  3227. s = s || o;
  3228. if (o.length !== undefined) {
  3229. // Indexed arrays, needed for Safari
  3230. for (n = 0, l = o.length; n < l; n++) {
  3231. if (cb.call(s, o[n], n, o) === false) {
  3232. return 0;
  3233. }
  3234. }
  3235. } else {
  3236. // Hashtables
  3237. for (n in o) {
  3238. if (o.hasOwnProperty(n)) {
  3239. if (cb.call(s, o[n], n, o) === false) {
  3240. return 0;
  3241. }
  3242. }
  3243. }
  3244. }
  3245. return 1;
  3246. }
  3247. /**
  3248. * Creates a new array by the return value of each iteration function call. This enables you to convert
  3249. * one array list into another.
  3250. *
  3251. * @method map
  3252. * @param {Array} a Array of items to iterate.
  3253. * @param {function} f Function to call for each item. It's return value will be the new value.
  3254. * @return {Array} Array with new values based on function return values.
  3255. */
  3256. function map(a, f) {
  3257. var o = [];
  3258. each(a, function(v) {
  3259. o.push(f(v));
  3260. });
  3261. return o;
  3262. }
  3263. /**
  3264. * Filters out items from the input array by calling the specified function for each item.
  3265. * If the function returns false the item will be excluded if it returns true it will be included.
  3266. *
  3267. * @method grep
  3268. * @param {Array} a Array of items to loop though.
  3269. * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
  3270. * @return {Array} New array with values imported and filtered based in input.
  3271. * @example
  3272. * // Filter out some items, this will return an array with 4 and 5
  3273. * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
  3274. */
  3275. function grep(a, f) {
  3276. var o = [];
  3277. each(a, function(v) {
  3278. if (!f || f(v)) {
  3279. o.push(v);
  3280. }
  3281. });
  3282. return o;
  3283. }
  3284. /**
  3285. * Creates a class, subclass or static singleton.
  3286. * More details on this method can be found in the Wiki.
  3287. *
  3288. * @method create
  3289. * @param {String} s Class name, inheritage and prefix.
  3290. * @param {Object} p Collection of methods to add to the class.
  3291. * @param {Object} root Optional root object defaults to the global window object.
  3292. * @example
  3293. * // Creates a basic class
  3294. * tinymce.create('tinymce.somepackage.SomeClass', {
  3295. * SomeClass: function() {
  3296. * // Class constructor
  3297. * },
  3298. *
  3299. * method: function() {
  3300. * // Some method
  3301. * }
  3302. * });
  3303. *
  3304. * // Creates a basic subclass class
  3305. * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
  3306. * SomeSubClass: function() {
  3307. * // Class constructor
  3308. * this.parent(); // Call parent constructor
  3309. * },
  3310. *
  3311. * method: function() {
  3312. * // Some method
  3313. * this.parent(); // Call parent method
  3314. * },
  3315. *
  3316. * 'static': {
  3317. * staticMethod: function() {
  3318. * // Static method
  3319. * }
  3320. * }
  3321. * });
  3322. *
  3323. * // Creates a singleton/static class
  3324. * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
  3325. * method: function() {
  3326. * // Some method
  3327. * }
  3328. * });
  3329. */
  3330. function create(s, p, root) {
  3331. var self = this, sp, ns, cn, scn, c, de = 0;
  3332. // Parse : <prefix> <class>:<super class>
  3333. s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
  3334. cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
  3335. // Create namespace for new class
  3336. ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
  3337. // Class already exists
  3338. if (ns[cn]) {
  3339. return;
  3340. }
  3341. // Make pure static class
  3342. if (s[2] == 'static') {
  3343. ns[cn] = p;
  3344. if (this.onCreate) {
  3345. this.onCreate(s[2], s[3], ns[cn]);
  3346. }
  3347. return;
  3348. }
  3349. // Create default constructor
  3350. if (!p[cn]) {
  3351. p[cn] = function() {};
  3352. de = 1;
  3353. }
  3354. // Add constructor and methods
  3355. ns[cn] = p[cn];
  3356. self.extend(ns[cn].prototype, p);
  3357. // Extend
  3358. if (s[5]) {
  3359. sp = self.resolve(s[5]).prototype;
  3360. scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
  3361. // Extend constructor
  3362. c = ns[cn];
  3363. if (de) {
  3364. // Add passthrough constructor
  3365. ns[cn] = function() {
  3366. return sp[scn].apply(this, arguments);
  3367. };
  3368. } else {
  3369. // Add inherit constructor
  3370. ns[cn] = function() {
  3371. this.parent = sp[scn];
  3372. return c.apply(this, arguments);
  3373. };
  3374. }
  3375. ns[cn].prototype[cn] = ns[cn];
  3376. // Add super methods
  3377. self.each(sp, function(f, n) {
  3378. ns[cn].prototype[n] = sp[n];
  3379. });
  3380. // Add overridden methods
  3381. self.each(p, function(f, n) {
  3382. // Extend methods if needed
  3383. if (sp[n]) {
  3384. ns[cn].prototype[n] = function() {
  3385. this.parent = sp[n];
  3386. return f.apply(this, arguments);
  3387. };
  3388. } else {
  3389. if (n != cn) {
  3390. ns[cn].prototype[n] = f;
  3391. }
  3392. }
  3393. });
  3394. }
  3395. // Add static methods
  3396. /*jshint sub:true*/
  3397. self.each(p['static'], function(f, n) {
  3398. ns[cn][n] = f;
  3399. });
  3400. }
  3401. /**
  3402. * Returns the index of a value in an array, this method will return -1 if the item wasn't found.
  3403. *
  3404. * @method inArray
  3405. * @param {Array} a Array/Object to search for value in.
  3406. * @param {Object} v Value to check for inside the array.
  3407. * @return {Number/String} Index of item inside the array inside an object. Or -1 if it wasn't found.
  3408. * @example
  3409. * // Get index of value in array this will alert 1 since 2 is at that index
  3410. * alert(tinymce.inArray([1,2,3], 2));
  3411. */
  3412. function inArray(a, v) {
  3413. var i, l;
  3414. if (a) {
  3415. for (i = 0, l = a.length; i < l; i++) {
  3416. if (a[i] === v) {
  3417. return i;
  3418. }
  3419. }
  3420. }
  3421. return -1;
  3422. }
  3423. function extend(obj, ext) {
  3424. var i, l, name, args = arguments, value;
  3425. for (i = 1, l = args.length; i < l; i++) {
  3426. ext = args[i];
  3427. for (name in ext) {
  3428. if (ext.hasOwnProperty(name)) {
  3429. value = ext[name];
  3430. if (value !== undefined) {
  3431. obj[name] = value;
  3432. }
  3433. }
  3434. }
  3435. }
  3436. return obj;
  3437. }
  3438. /**
  3439. * Executed the specified function for each item in a object tree.
  3440. *
  3441. * @method walk
  3442. * @param {Object} o Object tree to walk though.
  3443. * @param {function} f Function to call for each item.
  3444. * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
  3445. * @param {String} s Optional scope to execute the function in.
  3446. */
  3447. function walk(o, f, n, s) {
  3448. s = s || this;
  3449. if (o) {
  3450. if (n) {
  3451. o = o[n];
  3452. }
  3453. each(o, function(o, i) {
  3454. if (f.call(s, o, i, n) === false) {
  3455. return false;
  3456. }
  3457. walk(o, f, n, s);
  3458. });
  3459. }
  3460. }
  3461. /**
  3462. * Creates a namespace on a specific object.
  3463. *
  3464. * @method createNS
  3465. * @param {String} n Namespace to create for example a.b.c.d.
  3466. * @param {Object} o Optional object to add namespace to, defaults to window.
  3467. * @return {Object} New namespace object the last item in path.
  3468. * @example
  3469. * // Create some namespace
  3470. * tinymce.createNS('tinymce.somepackage.subpackage');
  3471. *
  3472. * // Add a singleton
  3473. * var tinymce.somepackage.subpackage.SomeSingleton = {
  3474. * method: function() {
  3475. * // Some method
  3476. * }
  3477. * };
  3478. */
  3479. function createNS(n, o) {
  3480. var i, v;
  3481. o = o || window;
  3482. n = n.split('.');
  3483. for (i = 0; i < n.length; i++) {
  3484. v = n[i];
  3485. if (!o[v]) {
  3486. o[v] = {};
  3487. }
  3488. o = o[v];
  3489. }
  3490. return o;
  3491. }
  3492. /**
  3493. * Resolves a string and returns the object from a specific structure.
  3494. *
  3495. * @method resolve
  3496. * @param {String} n Path to resolve for example a.b.c.d.
  3497. * @param {Object} o Optional object to search though, defaults to window.
  3498. * @return {Object} Last object in path or null if it couldn't be resolved.
  3499. * @example
  3500. * // Resolve a path into an object reference
  3501. * var obj = tinymce.resolve('a.b.c.d');
  3502. */
  3503. function resolve(n, o) {
  3504. var i, l;
  3505. o = o || window;
  3506. n = n.split('.');
  3507. for (i = 0, l = n.length; i < l; i++) {
  3508. o = o[n[i]];
  3509. if (!o) {
  3510. break;
  3511. }
  3512. }
  3513. return o;
  3514. }
  3515. /**
  3516. * Splits a string but removes the whitespace before and after each value.
  3517. *
  3518. * @method explode
  3519. * @param {string} s String to split.
  3520. * @param {string} d Delimiter to split by.
  3521. * @example
  3522. * // Split a string into an array with a,b,c
  3523. * var arr = tinymce.explode('a, b, c');
  3524. */
  3525. function explode(s, d) {
  3526. if (!s || is(s, 'array')) {
  3527. return s;
  3528. }
  3529. return map(s.split(d || ','), trim);
  3530. }
  3531. return {
  3532. trim: trim,
  3533. isArray: isArray,
  3534. is: is,
  3535. toArray: toArray,
  3536. makeMap: makeMap,
  3537. each: each,
  3538. map: map,
  3539. grep: grep,
  3540. inArray: inArray,
  3541. extend: extend,
  3542. create: create,
  3543. walk: walk,
  3544. createNS: createNS,
  3545. resolve: resolve,
  3546. explode: explode
  3547. };
  3548. });
  3549. // Included from: js/tinymce/classes/dom/Range.js
  3550. /**
  3551. * Range.js
  3552. *
  3553. * Copyright, Moxiecode Systems AB
  3554. * Released under LGPL License.
  3555. *
  3556. * License: http://www.tinymce.com/license
  3557. * Contributing: http://www.tinymce.com/contributing
  3558. */
  3559. define("tinymce/dom/Range", [
  3560. "tinymce/util/Tools"
  3561. ], function(Tools) {
  3562. // Range constructor
  3563. function Range(dom) {
  3564. var self = this,
  3565. doc = dom.doc,
  3566. EXTRACT = 0,
  3567. CLONE = 1,
  3568. DELETE = 2,
  3569. TRUE = true,
  3570. FALSE = false,
  3571. START_OFFSET = 'startOffset',
  3572. START_CONTAINER = 'startContainer',
  3573. END_CONTAINER = 'endContainer',
  3574. END_OFFSET = 'endOffset',
  3575. extend = Tools.extend,
  3576. nodeIndex = dom.nodeIndex;
  3577. function createDocumentFragment() {
  3578. return doc.createDocumentFragment();
  3579. }
  3580. function setStart(n, o) {
  3581. _setEndPoint(TRUE, n, o);
  3582. }
  3583. function setEnd(n, o) {
  3584. _setEndPoint(FALSE, n, o);
  3585. }
  3586. function setStartBefore(n) {
  3587. setStart(n.parentNode, nodeIndex(n));
  3588. }
  3589. function setStartAfter(n) {
  3590. setStart(n.parentNode, nodeIndex(n) + 1);
  3591. }
  3592. function setEndBefore(n) {
  3593. setEnd(n.parentNode, nodeIndex(n));
  3594. }
  3595. function setEndAfter(n) {
  3596. setEnd(n.parentNode, nodeIndex(n) + 1);
  3597. }
  3598. function collapse(ts) {
  3599. if (ts) {
  3600. self[END_CONTAINER] = self[START_CONTAINER];
  3601. self[END_OFFSET] = self[START_OFFSET];
  3602. } else {
  3603. self[START_CONTAINER] = self[END_CONTAINER];
  3604. self[START_OFFSET] = self[END_OFFSET];
  3605. }
  3606. self.collapsed = TRUE;
  3607. }
  3608. function selectNode(n) {
  3609. setStartBefore(n);
  3610. setEndAfter(n);
  3611. }
  3612. function selectNodeContents(n) {
  3613. setStart(n, 0);
  3614. setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
  3615. }
  3616. function compareBoundaryPoints(h, r) {
  3617. var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
  3618. rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
  3619. // Check START_TO_START
  3620. if (h === 0) {
  3621. return _compareBoundaryPoints(sc, so, rsc, rso);
  3622. }
  3623. // Check START_TO_END
  3624. if (h === 1) {
  3625. return _compareBoundaryPoints(ec, eo, rsc, rso);
  3626. }
  3627. // Check END_TO_END
  3628. if (h === 2) {
  3629. return _compareBoundaryPoints(ec, eo, rec, reo);
  3630. }
  3631. // Check END_TO_START
  3632. if (h === 3) {
  3633. return _compareBoundaryPoints(sc, so, rec, reo);
  3634. }
  3635. }
  3636. function deleteContents() {
  3637. _traverse(DELETE);
  3638. }
  3639. function extractContents() {
  3640. return _traverse(EXTRACT);
  3641. }
  3642. function cloneContents() {
  3643. return _traverse(CLONE);
  3644. }
  3645. function insertNode(n) {
  3646. var startContainer = this[START_CONTAINER],
  3647. startOffset = this[START_OFFSET], nn, o;
  3648. // Node is TEXT_NODE or CDATA
  3649. if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
  3650. if (!startOffset) {
  3651. // At the start of text
  3652. startContainer.parentNode.insertBefore(n, startContainer);
  3653. } else if (startOffset >= startContainer.nodeValue.length) {
  3654. // At the end of text
  3655. dom.insertAfter(n, startContainer);
  3656. } else {
  3657. // Middle, need to split
  3658. nn = startContainer.splitText(startOffset);
  3659. startContainer.parentNode.insertBefore(n, nn);
  3660. }
  3661. } else {
  3662. // Insert element node
  3663. if (startContainer.childNodes.length > 0) {
  3664. o = startContainer.childNodes[startOffset];
  3665. }
  3666. if (o) {
  3667. startContainer.insertBefore(n, o);
  3668. } else {
  3669. if (startContainer.nodeType == 3) {
  3670. dom.insertAfter(n, startContainer);
  3671. } else {
  3672. startContainer.appendChild(n);
  3673. }
  3674. }
  3675. }
  3676. }
  3677. function surroundContents(n) {
  3678. var f = self.extractContents();
  3679. self.insertNode(n);
  3680. n.appendChild(f);
  3681. self.selectNode(n);
  3682. }
  3683. function cloneRange() {
  3684. return extend(new Range(dom), {
  3685. startContainer: self[START_CONTAINER],
  3686. startOffset: self[START_OFFSET],
  3687. endContainer: self[END_CONTAINER],
  3688. endOffset: self[END_OFFSET],
  3689. collapsed: self.collapsed,
  3690. commonAncestorContainer: self.commonAncestorContainer
  3691. });
  3692. }
  3693. // Private methods
  3694. function _getSelectedNode(container, offset) {
  3695. var child;
  3696. if (container.nodeType == 3 /* TEXT_NODE */) {
  3697. return container;
  3698. }
  3699. if (offset < 0) {
  3700. return container;
  3701. }
  3702. child = container.firstChild;
  3703. while (child && offset > 0) {
  3704. --offset;
  3705. child = child.nextSibling;
  3706. }
  3707. if (child) {
  3708. return child;
  3709. }
  3710. return container;
  3711. }
  3712. function _isCollapsed() {
  3713. return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
  3714. }
  3715. function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
  3716. var c, offsetC, n, cmnRoot, childA, childB;
  3717. // In the first case the boundary-points have the same container. A is before B
  3718. // if its offset is less than the offset of B, A is equal to B if its offset is
  3719. // equal to the offset of B, and A is after B if its offset is greater than the
  3720. // offset of B.
  3721. if (containerA == containerB) {
  3722. if (offsetA == offsetB) {
  3723. return 0; // equal
  3724. }
  3725. if (offsetA < offsetB) {
  3726. return -1; // before
  3727. }
  3728. return 1; // after
  3729. }
  3730. // In the second case a child node C of the container of A is an ancestor
  3731. // container of B. In this case, A is before B if the offset of A is less than or
  3732. // equal to the index of the child node C and A is after B otherwise.
  3733. c = containerB;
  3734. while (c && c.parentNode != containerA) {
  3735. c = c.parentNode;
  3736. }
  3737. if (c) {
  3738. offsetC = 0;
  3739. n = containerA.firstChild;
  3740. while (n != c && offsetC < offsetA) {
  3741. offsetC++;
  3742. n = n.nextSibling;
  3743. }
  3744. if (offsetA <= offsetC) {
  3745. return -1; // before
  3746. }
  3747. return 1; // after
  3748. }
  3749. // In the third case a child node C of the container of B is an ancestor container
  3750. // of A. In this case, A is before B if the index of the child node C is less than
  3751. // the offset of B and A is after B otherwise.
  3752. c = containerA;
  3753. while (c && c.parentNode != containerB) {
  3754. c = c.parentNode;
  3755. }
  3756. if (c) {
  3757. offsetC = 0;
  3758. n = containerB.firstChild;
  3759. while (n != c && offsetC < offsetB) {
  3760. offsetC++;
  3761. n = n.nextSibling;
  3762. }
  3763. if (offsetC < offsetB) {
  3764. return -1; // before
  3765. }
  3766. return 1; // after
  3767. }
  3768. // In the fourth case, none of three other cases hold: the containers of A and B
  3769. // are siblings or descendants of sibling nodes. In this case, A is before B if
  3770. // the container of A is before the container of B in a pre-order traversal of the
  3771. // Ranges' context tree and A is after B otherwise.
  3772. cmnRoot = dom.findCommonAncestor(containerA, containerB);
  3773. childA = containerA;
  3774. while (childA && childA.parentNode != cmnRoot) {
  3775. childA = childA.parentNode;
  3776. }
  3777. if (!childA) {
  3778. childA = cmnRoot;
  3779. }
  3780. childB = containerB;
  3781. while (childB && childB.parentNode != cmnRoot) {
  3782. childB = childB.parentNode;
  3783. }
  3784. if (!childB) {
  3785. childB = cmnRoot;
  3786. }
  3787. if (childA == childB) {
  3788. return 0; // equal
  3789. }
  3790. n = cmnRoot.firstChild;
  3791. while (n) {
  3792. if (n == childA) {
  3793. return -1; // before
  3794. }
  3795. if (n == childB) {
  3796. return 1; // after
  3797. }
  3798. n = n.nextSibling;
  3799. }
  3800. }
  3801. function _setEndPoint(st, n, o) {
  3802. var ec, sc;
  3803. if (st) {
  3804. self[START_CONTAINER] = n;
  3805. self[START_OFFSET] = o;
  3806. } else {
  3807. self[END_CONTAINER] = n;
  3808. self[END_OFFSET] = o;
  3809. }
  3810. // If one boundary-point of a Range is set to have a root container
  3811. // other than the current one for the Range, the Range is collapsed to
  3812. // the new position. This enforces the restriction that both boundary-
  3813. // points of a Range must have the same root container.
  3814. ec = self[END_CONTAINER];
  3815. while (ec.parentNode) {
  3816. ec = ec.parentNode;
  3817. }
  3818. sc = self[START_CONTAINER];
  3819. while (sc.parentNode) {
  3820. sc = sc.parentNode;
  3821. }
  3822. if (sc == ec) {
  3823. // The start position of a Range is guaranteed to never be after the
  3824. // end position. To enforce this restriction, if the start is set to
  3825. // be at a position after the end, the Range is collapsed to that
  3826. // position.
  3827. if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
  3828. self.collapse(st);
  3829. }
  3830. } else {
  3831. self.collapse(st);
  3832. }
  3833. self.collapsed = _isCollapsed();
  3834. self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
  3835. }
  3836. function _traverse(how) {
  3837. var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
  3838. if (self[START_CONTAINER] == self[END_CONTAINER]) {
  3839. return _traverseSameContainer(how);
  3840. }
  3841. for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  3842. if (p == self[START_CONTAINER]) {
  3843. return _traverseCommonStartContainer(c, how);
  3844. }
  3845. ++endContainerDepth;
  3846. }
  3847. for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
  3848. if (p == self[END_CONTAINER]) {
  3849. return _traverseCommonEndContainer(c, how);
  3850. }
  3851. ++startContainerDepth;
  3852. }
  3853. depthDiff = startContainerDepth - endContainerDepth;
  3854. startNode = self[START_CONTAINER];
  3855. while (depthDiff > 0) {
  3856. startNode = startNode.parentNode;
  3857. depthDiff--;
  3858. }
  3859. endNode = self[END_CONTAINER];
  3860. while (depthDiff < 0) {
  3861. endNode = endNode.parentNode;
  3862. depthDiff++;
  3863. }
  3864. // ascend the ancestor hierarchy until we have a common parent.
  3865. for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
  3866. startNode = sp;
  3867. endNode = ep;
  3868. }
  3869. return _traverseCommonAncestors(startNode, endNode, how);
  3870. }
  3871. function _traverseSameContainer(how) {
  3872. var frag, s, sub, n, cnt, sibling, xferNode, start, len;
  3873. if (how != DELETE) {
  3874. frag = createDocumentFragment();
  3875. }
  3876. // If selection is empty, just return the fragment
  3877. if (self[START_OFFSET] == self[END_OFFSET]) {
  3878. return frag;
  3879. }
  3880. // Text node needs special case handling
  3881. if (self[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
  3882. // get the substring
  3883. s = self[START_CONTAINER].nodeValue;
  3884. sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
  3885. // set the original text node to its new value
  3886. if (how != CLONE) {
  3887. n = self[START_CONTAINER];
  3888. start = self[START_OFFSET];
  3889. len = self[END_OFFSET] - self[START_OFFSET];
  3890. if (start === 0 && len >= n.nodeValue.length - 1) {
  3891. n.parentNode.removeChild(n);
  3892. } else {
  3893. n.deleteData(start, len);
  3894. }
  3895. // Nothing is partially selected, so collapse to start point
  3896. self.collapse(TRUE);
  3897. }
  3898. if (how == DELETE) {
  3899. return;
  3900. }
  3901. if (sub.length > 0) {
  3902. frag.appendChild(doc.createTextNode(sub));
  3903. }
  3904. return frag;
  3905. }
  3906. // Copy nodes between the start/end offsets.
  3907. n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
  3908. cnt = self[END_OFFSET] - self[START_OFFSET];
  3909. while (n && cnt > 0) {
  3910. sibling = n.nextSibling;
  3911. xferNode = _traverseFullySelected(n, how);
  3912. if (frag) {
  3913. frag.appendChild(xferNode);
  3914. }
  3915. --cnt;
  3916. n = sibling;
  3917. }
  3918. // Nothing is partially selected, so collapse to start point
  3919. if (how != CLONE) {
  3920. self.collapse(TRUE);
  3921. }
  3922. return frag;
  3923. }
  3924. function _traverseCommonStartContainer(endAncestor, how) {
  3925. var frag, n, endIdx, cnt, sibling, xferNode;
  3926. if (how != DELETE) {
  3927. frag = createDocumentFragment();
  3928. }
  3929. n = _traverseRightBoundary(endAncestor, how);
  3930. if (frag) {
  3931. frag.appendChild(n);
  3932. }
  3933. endIdx = nodeIndex(endAncestor);
  3934. cnt = endIdx - self[START_OFFSET];
  3935. if (cnt <= 0) {
  3936. // Collapse to just before the endAncestor, which
  3937. // is partially selected.
  3938. if (how != CLONE) {
  3939. self.setEndBefore(endAncestor);
  3940. self.collapse(FALSE);
  3941. }
  3942. return frag;
  3943. }
  3944. n = endAncestor.previousSibling;
  3945. while (cnt > 0) {
  3946. sibling = n.previousSibling;
  3947. xferNode = _traverseFullySelected(n, how);
  3948. if (frag) {
  3949. frag.insertBefore(xferNode, frag.firstChild);
  3950. }
  3951. --cnt;
  3952. n = sibling;
  3953. }
  3954. // Collapse to just before the endAncestor, which
  3955. // is partially selected.
  3956. if (how != CLONE) {
  3957. self.setEndBefore(endAncestor);
  3958. self.collapse(FALSE);
  3959. }
  3960. return frag;
  3961. }
  3962. function _traverseCommonEndContainer(startAncestor, how) {
  3963. var frag, startIdx, n, cnt, sibling, xferNode;
  3964. if (how != DELETE) {
  3965. frag = createDocumentFragment();
  3966. }
  3967. n = _traverseLeftBoundary(startAncestor, how);
  3968. if (frag) {
  3969. frag.appendChild(n);
  3970. }
  3971. startIdx = nodeIndex(startAncestor);
  3972. ++startIdx; // Because we already traversed it
  3973. cnt = self[END_OFFSET] - startIdx;
  3974. n = startAncestor.nextSibling;
  3975. while (n && cnt > 0) {
  3976. sibling = n.nextSibling;
  3977. xferNode = _traverseFullySelected(n, how);
  3978. if (frag) {
  3979. frag.appendChild(xferNode);
  3980. }
  3981. --cnt;
  3982. n = sibling;
  3983. }
  3984. if (how != CLONE) {
  3985. self.setStartAfter(startAncestor);
  3986. self.collapse(TRUE);
  3987. }
  3988. return frag;
  3989. }
  3990. function _traverseCommonAncestors(startAncestor, endAncestor, how) {
  3991. var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
  3992. if (how != DELETE) {
  3993. frag = createDocumentFragment();
  3994. }
  3995. n = _traverseLeftBoundary(startAncestor, how);
  3996. if (frag) {
  3997. frag.appendChild(n);
  3998. }
  3999. startOffset = nodeIndex(startAncestor);
  4000. endOffset = nodeIndex(endAncestor);
  4001. ++startOffset;
  4002. cnt = endOffset - startOffset;
  4003. sibling = startAncestor.nextSibling;
  4004. while (cnt > 0) {
  4005. nextSibling = sibling.nextSibling;
  4006. n = _traverseFullySelected(sibling, how);
  4007. if (frag) {
  4008. frag.appendChild(n);
  4009. }
  4010. sibling = nextSibling;
  4011. --cnt;
  4012. }
  4013. n = _traverseRightBoundary(endAncestor, how);
  4014. if (frag) {
  4015. frag.appendChild(n);
  4016. }
  4017. if (how != CLONE) {
  4018. self.setStartAfter(startAncestor);
  4019. self.collapse(TRUE);
  4020. }
  4021. return frag;
  4022. }
  4023. function _traverseRightBoundary(root, how) {
  4024. var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
  4025. var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
  4026. if (next == root) {
  4027. return _traverseNode(next, isFullySelected, FALSE, how);
  4028. }
  4029. parent = next.parentNode;
  4030. clonedParent = _traverseNode(parent, FALSE, FALSE, how);
  4031. while (parent) {
  4032. while (next) {
  4033. prevSibling = next.previousSibling;
  4034. clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
  4035. if (how != DELETE) {
  4036. clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
  4037. }
  4038. isFullySelected = TRUE;
  4039. next = prevSibling;
  4040. }
  4041. if (parent == root) {
  4042. return clonedParent;
  4043. }
  4044. next = parent.previousSibling;
  4045. parent = parent.parentNode;
  4046. clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
  4047. if (how != DELETE) {
  4048. clonedGrandParent.appendChild(clonedParent);
  4049. }
  4050. clonedParent = clonedGrandParent;
  4051. }
  4052. }
  4053. function _traverseLeftBoundary(root, how) {
  4054. var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
  4055. var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
  4056. if (next == root) {
  4057. return _traverseNode(next, isFullySelected, TRUE, how);
  4058. }
  4059. parent = next.parentNode;
  4060. clonedParent = _traverseNode(parent, FALSE, TRUE, how);
  4061. while (parent) {
  4062. while (next) {
  4063. nextSibling = next.nextSibling;
  4064. clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
  4065. if (how != DELETE) {
  4066. clonedParent.appendChild(clonedChild);
  4067. }
  4068. isFullySelected = TRUE;
  4069. next = nextSibling;
  4070. }
  4071. if (parent == root) {
  4072. return clonedParent;
  4073. }
  4074. next = parent.nextSibling;
  4075. parent = parent.parentNode;
  4076. clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
  4077. if (how != DELETE) {
  4078. clonedGrandParent.appendChild(clonedParent);
  4079. }
  4080. clonedParent = clonedGrandParent;
  4081. }
  4082. }
  4083. function _traverseNode(n, isFullySelected, isLeft, how) {
  4084. var txtValue, newNodeValue, oldNodeValue, offset, newNode;
  4085. if (isFullySelected) {
  4086. return _traverseFullySelected(n, how);
  4087. }
  4088. if (n.nodeType == 3 /* TEXT_NODE */) {
  4089. txtValue = n.nodeValue;
  4090. if (isLeft) {
  4091. offset = self[START_OFFSET];
  4092. newNodeValue = txtValue.substring(offset);
  4093. oldNodeValue = txtValue.substring(0, offset);
  4094. } else {
  4095. offset = self[END_OFFSET];
  4096. newNodeValue = txtValue.substring(0, offset);
  4097. oldNodeValue = txtValue.substring(offset);
  4098. }
  4099. if (how != CLONE) {
  4100. n.nodeValue = oldNodeValue;
  4101. }
  4102. if (how == DELETE) {
  4103. return;
  4104. }
  4105. newNode = dom.clone(n, FALSE);
  4106. newNode.nodeValue = newNodeValue;
  4107. return newNode;
  4108. }
  4109. if (how == DELETE) {
  4110. return;
  4111. }
  4112. return dom.clone(n, FALSE);
  4113. }
  4114. function _traverseFullySelected(n, how) {
  4115. if (how != DELETE) {
  4116. return how == CLONE ? dom.clone(n, TRUE) : n;
  4117. }
  4118. n.parentNode.removeChild(n);
  4119. }
  4120. function toStringIE() {
  4121. return dom.create('body', null, cloneContents()).outerText;
  4122. }
  4123. extend(self, {
  4124. // Inital states
  4125. startContainer: doc,
  4126. startOffset: 0,
  4127. endContainer: doc,
  4128. endOffset: 0,
  4129. collapsed: TRUE,
  4130. commonAncestorContainer: doc,
  4131. // Range constants
  4132. START_TO_START: 0,
  4133. START_TO_END: 1,
  4134. END_TO_END: 2,
  4135. END_TO_START: 3,
  4136. // Public methods
  4137. setStart: setStart,
  4138. setEnd: setEnd,
  4139. setStartBefore: setStartBefore,
  4140. setStartAfter: setStartAfter,
  4141. setEndBefore: setEndBefore,
  4142. setEndAfter: setEndAfter,
  4143. collapse: collapse,
  4144. selectNode: selectNode,
  4145. selectNodeContents: selectNodeContents,
  4146. compareBoundaryPoints: compareBoundaryPoints,
  4147. deleteContents: deleteContents,
  4148. extractContents: extractContents,
  4149. cloneContents: cloneContents,
  4150. insertNode: insertNode,
  4151. surroundContents: surroundContents,
  4152. cloneRange: cloneRange,
  4153. toStringIE: toStringIE
  4154. });
  4155. return self;
  4156. }
  4157. // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
  4158. Range.prototype.toString = function() {
  4159. return this.toStringIE();
  4160. };
  4161. return Range;
  4162. });
  4163. // Included from: js/tinymce/classes/html/Entities.js
  4164. /**
  4165. * Entities.js
  4166. *
  4167. * Copyright, Moxiecode Systems AB
  4168. * Released under LGPL License.
  4169. *
  4170. * License: http://www.tinymce.com/license
  4171. * Contributing: http://www.tinymce.com/contributing
  4172. */
  4173. /*jshint bitwise:false */
  4174. /*eslint no-bitwise:0 */
  4175. /**
  4176. * Entity encoder class.
  4177. *
  4178. * @class tinymce.html.Entities
  4179. * @static
  4180. * @version 3.4
  4181. */
  4182. define("tinymce/html/Entities", [
  4183. "tinymce/util/Tools"
  4184. ], function(Tools) {
  4185. var makeMap = Tools.makeMap;
  4186. var namedEntities, baseEntities, reverseEntities,
  4187. attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  4188. textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
  4189. rawCharsRegExp = /[<>&\"\']/g,
  4190. entityRegExp = /&(#x|#)?([\w]+);/g,
  4191. asciiMap = {
  4192. 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
  4193. 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
  4194. 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
  4195. 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
  4196. 156: "\u0153", 158: "\u017E", 159: "\u0178"
  4197. };
  4198. // Raw entities
  4199. baseEntities = {
  4200. '\"': '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
  4201. "'": '&#39;',
  4202. '<': '&lt;',
  4203. '>': '&gt;',
  4204. '&': '&amp;'
  4205. };
  4206. // Reverse lookup table for raw entities
  4207. reverseEntities = {
  4208. '&lt;': '<',
  4209. '&gt;': '>',
  4210. '&amp;': '&',
  4211. '&quot;': '"',
  4212. '&apos;': "'"
  4213. };
  4214. // Decodes text by using the browser
  4215. function nativeDecode(text) {
  4216. var elm;
  4217. elm = document.createElement("div");
  4218. elm.innerHTML = text;
  4219. return elm.textContent || elm.innerText || text;
  4220. }
  4221. // Build a two way lookup table for the entities
  4222. function buildEntitiesLookup(items, radix) {
  4223. var i, chr, entity, lookup = {};
  4224. if (items) {
  4225. items = items.split(',');
  4226. radix = radix || 10;
  4227. // Build entities lookup table
  4228. for (i = 0; i < items.length; i += 2) {
  4229. chr = String.fromCharCode(parseInt(items[i], radix));
  4230. // Only add non base entities
  4231. if (!baseEntities[chr]) {
  4232. entity = '&' + items[i + 1] + ';';
  4233. lookup[chr] = entity;
  4234. lookup[entity] = chr;
  4235. }
  4236. }
  4237. return lookup;
  4238. }
  4239. }
  4240. // Unpack entities lookup where the numbers are in radix 32 to reduce the size
  4241. namedEntities = buildEntitiesLookup(
  4242. '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
  4243. '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
  4244. '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
  4245. '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
  4246. '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
  4247. '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
  4248. '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
  4249. '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
  4250. '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
  4251. '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
  4252. 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
  4253. 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
  4254. 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
  4255. 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
  4256. 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
  4257. '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
  4258. '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
  4259. '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
  4260. '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
  4261. '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
  4262. 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
  4263. 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
  4264. 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
  4265. '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
  4266. '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
  4267. var Entities = {
  4268. /**
  4269. * Encodes the specified string using raw entities. This means only the required XML base entities will be endoded.
  4270. *
  4271. * @method encodeRaw
  4272. * @param {String} text Text to encode.
  4273. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  4274. * @return {String} Entity encoded text.
  4275. */
  4276. encodeRaw: function(text, attr) {
  4277. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4278. return baseEntities[chr] || chr;
  4279. });
  4280. },
  4281. /**
  4282. * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
  4283. * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
  4284. * and is exposed as the DOMUtils.encode function.
  4285. *
  4286. * @method encodeAllRaw
  4287. * @param {String} text Text to encode.
  4288. * @return {String} Entity encoded text.
  4289. */
  4290. encodeAllRaw: function(text) {
  4291. return ('' + text).replace(rawCharsRegExp, function(chr) {
  4292. return baseEntities[chr] || chr;
  4293. });
  4294. },
  4295. /**
  4296. * Encodes the specified string using numeric entities. The core entities will be
  4297. * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
  4298. *
  4299. * @method encodeNumeric
  4300. * @param {String} text Text to encode.
  4301. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  4302. * @return {String} Entity encoded text.
  4303. */
  4304. encodeNumeric: function(text, attr) {
  4305. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4306. // Multi byte sequence convert it to a single entity
  4307. if (chr.length > 1) {
  4308. return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
  4309. }
  4310. return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
  4311. });
  4312. },
  4313. /**
  4314. * Encodes the specified string using named entities. The core entities will be encoded
  4315. * as named ones but all non lower ascii characters will be encoded into named entities.
  4316. *
  4317. * @method encodeNamed
  4318. * @param {String} text Text to encode.
  4319. * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
  4320. * @param {Object} entities Optional parameter with entities to use.
  4321. * @return {String} Entity encoded text.
  4322. */
  4323. encodeNamed: function(text, attr, entities) {
  4324. entities = entities || namedEntities;
  4325. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4326. return baseEntities[chr] || entities[chr] || chr;
  4327. });
  4328. },
  4329. /**
  4330. * Returns an encode function based on the name(s) and it's optional entities.
  4331. *
  4332. * @method getEncodeFunc
  4333. * @param {String} name Comma separated list of encoders for example named,numeric.
  4334. * @param {String} entities Optional parameter with entities to use instead of the built in set.
  4335. * @return {function} Encode function to be used.
  4336. */
  4337. getEncodeFunc: function(name, entities) {
  4338. entities = buildEntitiesLookup(entities) || namedEntities;
  4339. function encodeNamedAndNumeric(text, attr) {
  4340. return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
  4341. return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
  4342. });
  4343. }
  4344. function encodeCustomNamed(text, attr) {
  4345. return Entities.encodeNamed(text, attr, entities);
  4346. }
  4347. // Replace + with , to be compatible with previous TinyMCE versions
  4348. name = makeMap(name.replace(/\+/g, ','));
  4349. // Named and numeric encoder
  4350. if (name.named && name.numeric) {
  4351. return encodeNamedAndNumeric;
  4352. }
  4353. // Named encoder
  4354. if (name.named) {
  4355. // Custom names
  4356. if (entities) {
  4357. return encodeCustomNamed;
  4358. }
  4359. return Entities.encodeNamed;
  4360. }
  4361. // Numeric
  4362. if (name.numeric) {
  4363. return Entities.encodeNumeric;
  4364. }
  4365. // Raw encoder
  4366. return Entities.encodeRaw;
  4367. },
  4368. /**
  4369. * Decodes the specified string, this will replace entities with raw UTF characters.
  4370. *
  4371. * @method decode
  4372. * @param {String} text Text to entity decode.
  4373. * @return {String} Entity decoded string.
  4374. */
  4375. decode: function(text) {
  4376. return text.replace(entityRegExp, function(all, numeric, value) {
  4377. if (numeric) {
  4378. value = parseInt(value, numeric.length === 2 ? 16 : 10);
  4379. // Support upper UTF
  4380. if (value > 0xFFFF) {
  4381. value -= 0x10000;
  4382. return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
  4383. } else {
  4384. return asciiMap[value] || String.fromCharCode(value);
  4385. }
  4386. }
  4387. return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
  4388. });
  4389. }
  4390. };
  4391. return Entities;
  4392. });
  4393. // Included from: js/tinymce/classes/Env.js
  4394. /**
  4395. * Env.js
  4396. *
  4397. * Copyright, Moxiecode Systems AB
  4398. * Released under LGPL License.
  4399. *
  4400. * License: http://www.tinymce.com/license
  4401. * Contributing: http://www.tinymce.com/contributing
  4402. */
  4403. /**
  4404. * This class contains various environment constants like browser versions etc.
  4405. * Normally you don't want to sniff specific browser versions but sometimes you have
  4406. * to when it's impossible to feature detect. So use this with care.
  4407. *
  4408. * @class tinymce.Env
  4409. * @static
  4410. */
  4411. define("tinymce/Env", [], function() {
  4412. var nav = navigator, userAgent = nav.userAgent;
  4413. var opera, webkit, ie, ie11, gecko, mac, iDevice;
  4414. opera = window.opera && window.opera.buildNumber;
  4415. webkit = /WebKit/.test(userAgent);
  4416. ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
  4417. ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
  4418. ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
  4419. ie = ie || ie11;
  4420. gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
  4421. mac = userAgent.indexOf('Mac') != -1;
  4422. iDevice = /(iPad|iPhone)/.test(userAgent);
  4423. // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
  4424. // says it has contentEditable support but there is no visible caret.
  4425. var contentEditable = !iDevice || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
  4426. return {
  4427. /**
  4428. * Constant that is true if the browser is Opera.
  4429. *
  4430. * @property opera
  4431. * @type Boolean
  4432. * @final
  4433. */
  4434. opera: opera,
  4435. /**
  4436. * Constant that is true if the browser is WebKit (Safari/Chrome).
  4437. *
  4438. * @property webKit
  4439. * @type Boolean
  4440. * @final
  4441. */
  4442. webkit: webkit,
  4443. /**
  4444. * Constant that is more than zero if the browser is IE.
  4445. *
  4446. * @property ie
  4447. * @type Boolean
  4448. * @final
  4449. */
  4450. ie: ie,
  4451. /**
  4452. * Constant that is true if the browser is Gecko.
  4453. *
  4454. * @property gecko
  4455. * @type Boolean
  4456. * @final
  4457. */
  4458. gecko: gecko,
  4459. /**
  4460. * Constant that is true if the os is Mac OS.
  4461. *
  4462. * @property mac
  4463. * @type Boolean
  4464. * @final
  4465. */
  4466. mac: mac,
  4467. /**
  4468. * Constant that is true if the os is iOS.
  4469. *
  4470. * @property iOS
  4471. * @type Boolean
  4472. * @final
  4473. */
  4474. iOS: iDevice,
  4475. /**
  4476. * Constant that is true if the browser supports editing.
  4477. *
  4478. * @property contentEditable
  4479. * @type Boolean
  4480. * @final
  4481. */
  4482. contentEditable: contentEditable,
  4483. /**
  4484. * Transparent image data url.
  4485. *
  4486. * @property transparentSrc
  4487. * @type Boolean
  4488. * @final
  4489. */
  4490. transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
  4491. /**
  4492. * Returns true/false if the browser can or can't place the caret after a inline block like an image.
  4493. *
  4494. * @property noCaretAfter
  4495. * @type Boolean
  4496. * @final
  4497. */
  4498. caretAfter: ie != 8,
  4499. /**
  4500. * Constant that is true if the browser supports native DOM Ranges. IE 9+.
  4501. *
  4502. * @property range
  4503. * @type Boolean
  4504. */
  4505. range: window.getSelection && "Range" in window,
  4506. /**
  4507. * Returns the IE document mode for non IE browsers this will fake IE 10.
  4508. *
  4509. * @property documentMode
  4510. * @type Number
  4511. */
  4512. documentMode: ie ? (document.documentMode || 7) : 10
  4513. };
  4514. });
  4515. // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
  4516. /**
  4517. * StyleSheetLoader.js
  4518. *
  4519. * Copyright, Moxiecode Systems AB
  4520. * Released under LGPL License.
  4521. *
  4522. * License: http://www.tinymce.com/license
  4523. * Contributing: http://www.tinymce.com/contributing
  4524. */
  4525. /**
  4526. * This class handles loading of external stylesheets and fires events when these are loaded.
  4527. *
  4528. * @class tinymce.dom.StyleSheetLoader
  4529. * @private
  4530. */
  4531. define("tinymce/dom/StyleSheetLoader", [], function() {
  4532. "use strict";
  4533. return function(document, settings) {
  4534. var idCount = 0, loadedStates = {}, maxLoadTime;
  4535. settings = settings || {};
  4536. maxLoadTime = settings.maxLoadTime || 5000;
  4537. function appendToHead(node) {
  4538. document.getElementsByTagName('head')[0].appendChild(node);
  4539. }
  4540. /**
  4541. * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
  4542. *
  4543. * @method load
  4544. * @param {String} url Url to be loaded.
  4545. * @param {Function} loadedCallback Callback to be executed when loaded.
  4546. * @param {Function} errorCallback Callback to be executed when failed loading.
  4547. */
  4548. function load(url, loadedCallback, errorCallback) {
  4549. var link, style, startTime, state;
  4550. function passed() {
  4551. var callbacks = state.passed, i = callbacks.length;
  4552. while (i--) {
  4553. callbacks[i]();
  4554. }
  4555. state.status = 2;
  4556. state.passed = [];
  4557. state.failed = [];
  4558. }
  4559. function failed() {
  4560. var callbacks = state.failed, i = callbacks.length;
  4561. while (i--) {
  4562. callbacks[i]();
  4563. }
  4564. state.status = 3;
  4565. state.passed = [];
  4566. state.failed = [];
  4567. }
  4568. // Sniffs for older WebKit versions that have the link.onload but a broken one
  4569. function isOldWebKit() {
  4570. var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
  4571. return !!(webKitChunks && webKitChunks[1] < 536);
  4572. }
  4573. // Calls the waitCallback until the test returns true or the timeout occurs
  4574. function wait(testCallback, waitCallback) {
  4575. if (!testCallback()) {
  4576. // Wait for timeout
  4577. if ((new Date().getTime()) - startTime < maxLoadTime) {
  4578. window.setTimeout(waitCallback, 0);
  4579. } else {
  4580. failed();
  4581. }
  4582. }
  4583. }
  4584. // Workaround for WebKit that doesn't properly support the onload event for link elements
  4585. // Or WebKit that fires the onload event before the StyleSheet is added to the document
  4586. function waitForWebKitLinkLoaded() {
  4587. wait(function() {
  4588. var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
  4589. while (i--) {
  4590. styleSheet = styleSheets[i];
  4591. owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
  4592. if (owner && owner.id === link.id) {
  4593. passed();
  4594. return true;
  4595. }
  4596. }
  4597. }, waitForWebKitLinkLoaded);
  4598. }
  4599. // Workaround for older Geckos that doesn't have any onload event for StyleSheets
  4600. function waitForGeckoLinkLoaded() {
  4601. wait(function() {
  4602. try {
  4603. // Accessing the cssRules will throw an exception until the CSS file is loaded
  4604. var cssRules = style.sheet.cssRules;
  4605. passed();
  4606. return !!cssRules;
  4607. } catch (ex) {
  4608. // Ignore
  4609. }
  4610. }, waitForGeckoLinkLoaded);
  4611. }
  4612. if (!loadedStates[url]) {
  4613. state = {
  4614. passed: [],
  4615. failed: []
  4616. };
  4617. loadedStates[url] = state;
  4618. } else {
  4619. state = loadedStates[url];
  4620. }
  4621. if (loadedCallback) {
  4622. state.passed.push(loadedCallback);
  4623. }
  4624. if (errorCallback) {
  4625. state.failed.push(errorCallback);
  4626. }
  4627. // Is loading wait for it to pass
  4628. if (state.status == 1) {
  4629. return;
  4630. }
  4631. // Has finished loading and was success
  4632. if (state.status == 2) {
  4633. passed();
  4634. return;
  4635. }
  4636. // Has finished loading and was a failure
  4637. if (state.status == 3) {
  4638. failed();
  4639. return;
  4640. }
  4641. // Start loading
  4642. state.status = 1;
  4643. link = document.createElement('link');
  4644. link.rel = 'stylesheet';
  4645. link.type = 'text/css';
  4646. link.id = 'u' + (idCount++);
  4647. link.async = false;
  4648. link.defer = false;
  4649. startTime = new Date().getTime();
  4650. // Feature detect onload on link element and sniff older webkits since it has an broken onload event
  4651. if ("onload" in link && !isOldWebKit()) {
  4652. link.onload = waitForWebKitLinkLoaded;
  4653. link.onerror = failed;
  4654. } else {
  4655. // Sniff for old Firefox that doesn't support the onload event on link elements
  4656. // TODO: Remove this in the future when everyone uses modern browsers
  4657. if (navigator.userAgent.indexOf("Firefox") > 0) {
  4658. style = document.createElement('style');
  4659. style.textContent = '@import "' + url + '"';
  4660. waitForGeckoLinkLoaded();
  4661. appendToHead(style);
  4662. return;
  4663. } else {
  4664. // Use the id owner on older webkits
  4665. waitForWebKitLinkLoaded();
  4666. }
  4667. }
  4668. appendToHead(link);
  4669. link.href = url;
  4670. }
  4671. this.load = load;
  4672. };
  4673. });
  4674. // Included from: js/tinymce/classes/dom/DOMUtils.js
  4675. /**
  4676. * DOMUtils.js
  4677. *
  4678. * Copyright, Moxiecode Systems AB
  4679. * Released under LGPL License.
  4680. *
  4681. * License: http://www.tinymce.com/license
  4682. * Contributing: http://www.tinymce.com/contributing
  4683. */
  4684. /**
  4685. * Utility class for various DOM manipulation and retrieval functions.
  4686. *
  4687. * @class tinymce.dom.DOMUtils
  4688. * @example
  4689. * // Add a class to an element by id in the page
  4690. * tinymce.DOM.addClass('someid', 'someclass');
  4691. *
  4692. * // Add a class to an element by id inside the editor
  4693. * tinymce.activeEditor.dom.addClass('someid', 'someclass');
  4694. */
  4695. define("tinymce/dom/DOMUtils", [
  4696. "tinymce/dom/Sizzle",
  4697. "tinymce/html/Styles",
  4698. "tinymce/dom/EventUtils",
  4699. "tinymce/dom/TreeWalker",
  4700. "tinymce/dom/Range",
  4701. "tinymce/html/Entities",
  4702. "tinymce/Env",
  4703. "tinymce/util/Tools",
  4704. "tinymce/dom/StyleSheetLoader"
  4705. ], function(Sizzle, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
  4706. // Shorten names
  4707. var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim, extend = Tools.extend;
  4708. var isWebKit = Env.webkit, isIE = Env.ie;
  4709. var simpleSelectorRe = /^([a-z0-9],?)+$/i;
  4710. var whiteSpaceRegExp = /^[ \t\r\n]*$/;
  4711. var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
  4712. /**
  4713. * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
  4714. *
  4715. * @constructor
  4716. * @method DOMUtils
  4717. * @param {Document} d Document reference to bind the utility class to.
  4718. * @param {settings} s Optional settings collection.
  4719. */
  4720. function DOMUtils(doc, settings) {
  4721. var self = this, blockElementsMap;
  4722. self.doc = doc;
  4723. self.win = window;
  4724. self.files = {};
  4725. self.counter = 0;
  4726. self.stdMode = !isIE || doc.documentMode >= 8;
  4727. self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
  4728. self.hasOuterHTML = "outerHTML" in doc.createElement("a");
  4729. self.styleSheetLoader = new StyleSheetLoader(doc);
  4730. this.boundEvents = [];
  4731. self.settings = settings = extend({
  4732. keep_values: false,
  4733. hex_colors: 1
  4734. }, settings);
  4735. self.schema = settings.schema;
  4736. self.styles = new Styles({
  4737. url_converter: settings.url_converter,
  4738. url_converter_scope: settings.url_converter_scope
  4739. }, settings.schema);
  4740. self.fixDoc(doc);
  4741. self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
  4742. blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
  4743. /**
  4744. * Returns true/false if the specified element is a block element or not.
  4745. *
  4746. * @method isBlock
  4747. * @param {Node/String} node Element/Node to check.
  4748. * @return {Boolean} True/False state if the node is a block element or not.
  4749. */
  4750. self.isBlock = function(node) {
  4751. // Fix for #5446
  4752. if (!node) {
  4753. return false;
  4754. }
  4755. // This function is called in module pattern style since it might be executed with the wrong this scope
  4756. var type = node.nodeType;
  4757. // If it's a node then check the type and use the nodeName
  4758. if (type) {
  4759. return !!(type === 1 && blockElementsMap[node.nodeName]);
  4760. }
  4761. return !!blockElementsMap[node];
  4762. };
  4763. }
  4764. DOMUtils.prototype = {
  4765. root: null,
  4766. props: {
  4767. "for": "htmlFor",
  4768. "class": "className",
  4769. className: "className",
  4770. checked: "checked",
  4771. disabled: "disabled",
  4772. maxlength: "maxLength",
  4773. readonly: "readOnly",
  4774. selected: "selected",
  4775. value: "value",
  4776. id: "id",
  4777. name: "name",
  4778. type: "type"
  4779. },
  4780. fixDoc: function(doc) {
  4781. var settings = this.settings, name;
  4782. if (isIE && settings.schema) {
  4783. // Add missing HTML 4/5 elements to IE
  4784. ('abbr article aside audio canvas ' +
  4785. 'details figcaption figure footer ' +
  4786. 'header hgroup mark menu meter nav ' +
  4787. 'output progress section summary ' +
  4788. 'time video').replace(/\w+/g, function(name) {
  4789. doc.createElement(name);
  4790. });
  4791. // Create all custom elements
  4792. for (name in settings.schema.getCustomElements()) {
  4793. doc.createElement(name);
  4794. }
  4795. }
  4796. },
  4797. clone: function(node, deep) {
  4798. var self = this, clone, doc;
  4799. // TODO: Add feature detection here in the future
  4800. if (!isIE || node.nodeType !== 1 || deep) {
  4801. return node.cloneNode(deep);
  4802. }
  4803. doc = self.doc;
  4804. // Make a HTML5 safe shallow copy
  4805. if (!deep) {
  4806. clone = doc.createElement(node.nodeName);
  4807. // Copy attribs
  4808. each(self.getAttribs(node), function(attr) {
  4809. self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
  4810. });
  4811. return clone;
  4812. }
  4813. /*
  4814. // Setup HTML5 patched document fragment
  4815. if (!self.frag) {
  4816. self.frag = doc.createDocumentFragment();
  4817. self.fixDoc(self.frag);
  4818. }
  4819. // Make a deep copy by adding it to the document fragment then removing it this removed the :section
  4820. clone = doc.createElement('div');
  4821. self.frag.appendChild(clone);
  4822. clone.innerHTML = node.outerHTML;
  4823. self.frag.removeChild(clone);
  4824. */
  4825. return clone.firstChild;
  4826. },
  4827. /**
  4828. * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
  4829. * go above the point of this root node.
  4830. *
  4831. * @method getRoot
  4832. * @return {Element} Root element for the utility class.
  4833. */
  4834. getRoot: function() {
  4835. var self = this;
  4836. return self.get(self.settings.root_element) || self.doc.body;
  4837. },
  4838. /**
  4839. * Returns the viewport of the window.
  4840. *
  4841. * @method getViewPort
  4842. * @param {Window} win Optional window to get viewport of.
  4843. * @return {Object} Viewport object with fields x, y, w and h.
  4844. */
  4845. getViewPort: function(win) {
  4846. var doc, rootElm;
  4847. win = !win ? this.win : win;
  4848. doc = win.document;
  4849. rootElm = this.boxModel ? doc.documentElement : doc.body;
  4850. // Returns viewport size excluding scrollbars
  4851. return {
  4852. x: win.pageXOffset || rootElm.scrollLeft,
  4853. y: win.pageYOffset || rootElm.scrollTop,
  4854. w: win.innerWidth || rootElm.clientWidth,
  4855. h: win.innerHeight || rootElm.clientHeight
  4856. };
  4857. },
  4858. /**
  4859. * Returns the rectangle for a specific element.
  4860. *
  4861. * @method getRect
  4862. * @param {Element/String} elm Element object or element ID to get rectangle from.
  4863. * @return {object} Rectangle for specified element object with x, y, w, h fields.
  4864. */
  4865. getRect: function(elm) {
  4866. var self = this, pos, size;
  4867. elm = self.get(elm);
  4868. pos = self.getPos(elm);
  4869. size = self.getSize(elm);
  4870. return {
  4871. x: pos.x, y: pos.y,
  4872. w: size.w, h: size.h
  4873. };
  4874. },
  4875. /**
  4876. * Returns the size dimensions of the specified element.
  4877. *
  4878. * @method getSize
  4879. * @param {Element/String} elm Element object or element ID to get rectangle from.
  4880. * @return {object} Rectangle for specified element object with w, h fields.
  4881. */
  4882. getSize: function(elm) {
  4883. var self = this, w, h;
  4884. elm = self.get(elm);
  4885. w = self.getStyle(elm, 'width');
  4886. h = self.getStyle(elm, 'height');
  4887. // Non pixel value, then force offset/clientWidth
  4888. if (w.indexOf('px') === -1) {
  4889. w = 0;
  4890. }
  4891. // Non pixel value, then force offset/clientWidth
  4892. if (h.indexOf('px') === -1) {
  4893. h = 0;
  4894. }
  4895. return {
  4896. w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
  4897. h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
  4898. };
  4899. },
  4900. /**
  4901. * Returns a node by the specified selector function. This function will
  4902. * loop through all parent nodes and call the specified function for each node.
  4903. * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
  4904. * and the node it found will be returned.
  4905. *
  4906. * @method getParent
  4907. * @param {Node/String} node DOM node to search parents on or ID string.
  4908. * @param {function} selector Selection function or CSS selector to execute on each node.
  4909. * @param {Node} root Optional root element, never go below this point.
  4910. * @return {Node} DOM Node or null if it wasn't found.
  4911. */
  4912. getParent: function(node, selector, root) {
  4913. return this.getParents(node, selector, root, false);
  4914. },
  4915. /**
  4916. * Returns a node list of all parents matching the specified selector function or pattern.
  4917. * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
  4918. *
  4919. * @method getParents
  4920. * @param {Node/String} node DOM node to search parents on or ID string.
  4921. * @param {function} selector Selection function to execute on each node or CSS pattern.
  4922. * @param {Node} root Optional root element, never go below this point.
  4923. * @return {Array} Array of nodes or null if it wasn't found.
  4924. */
  4925. getParents: function(node, selector, root, collect) {
  4926. var self = this, selectorVal, result = [];
  4927. node = self.get(node);
  4928. collect = collect === undefined;
  4929. // Default root on inline mode
  4930. root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
  4931. // Wrap node name as func
  4932. if (is(selector, 'string')) {
  4933. selectorVal = selector;
  4934. if (selector === '*') {
  4935. selector = function(node) {return node.nodeType == 1;};
  4936. } else {
  4937. selector = function(node) {
  4938. return self.is(node, selectorVal);
  4939. };
  4940. }
  4941. }
  4942. while (node) {
  4943. if (node == root || !node.nodeType || node.nodeType === 9) {
  4944. break;
  4945. }
  4946. if (!selector || selector(node)) {
  4947. if (collect) {
  4948. result.push(node);
  4949. } else {
  4950. return node;
  4951. }
  4952. }
  4953. node = node.parentNode;
  4954. }
  4955. return collect ? result : null;
  4956. },
  4957. /**
  4958. * Returns the specified element by ID or the input element if it isn't a string.
  4959. *
  4960. * @method get
  4961. * @param {String/Element} n Element id to look for or element to just pass though.
  4962. * @return {Element} Element matching the specified id or null if it wasn't found.
  4963. */
  4964. get: function(elm) {
  4965. var name;
  4966. if (elm && this.doc && typeof(elm) == 'string') {
  4967. name = elm;
  4968. elm = this.doc.getElementById(elm);
  4969. // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
  4970. if (elm && elm.id !== name) {
  4971. return this.doc.getElementsByName(name)[1];
  4972. }
  4973. }
  4974. return elm;
  4975. },
  4976. /**
  4977. * Returns the next node that matches selector or function
  4978. *
  4979. * @method getNext
  4980. * @param {Node} node Node to find siblings from.
  4981. * @param {String/function} selector Selector CSS expression or function.
  4982. * @return {Node} Next node item matching the selector or null if it wasn't found.
  4983. */
  4984. getNext: function(node, selector) {
  4985. return this._findSib(node, selector, 'nextSibling');
  4986. },
  4987. /**
  4988. * Returns the previous node that matches selector or function
  4989. *
  4990. * @method getPrev
  4991. * @param {Node} node Node to find siblings from.
  4992. * @param {String/function} selector Selector CSS expression or function.
  4993. * @return {Node} Previous node item matching the selector or null if it wasn't found.
  4994. */
  4995. getPrev: function(node, selector) {
  4996. return this._findSib(node, selector, 'previousSibling');
  4997. },
  4998. // #ifndef jquery
  4999. /**
  5000. * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
  5001. * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
  5002. * on more complex patterns.
  5003. *
  5004. * @method select
  5005. * @param {String} selector CSS level 3 pattern to select/find elements by.
  5006. * @param {Object} scope Optional root element/scope element to search in.
  5007. * @return {Array} Array with all matched elements.
  5008. * @example
  5009. * // Adds a class to all paragraphs in the currently active editor
  5010. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  5011. *
  5012. * // Adds a class to all spans that have the test class in the currently active editor
  5013. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
  5014. */
  5015. select: function(selector, scope) {
  5016. var self = this;
  5017. //Sizzle.selectors.cacheLength = 0;
  5018. return Sizzle(selector, self.get(scope) || self.get(self.settings.root_element) || self.doc, []);
  5019. },
  5020. /**
  5021. * Returns true/false if the specified element matches the specified css pattern.
  5022. *
  5023. * @method is
  5024. * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
  5025. * @param {String} selector CSS pattern to match the element against.
  5026. */
  5027. is: function(elm, selector) {
  5028. var i;
  5029. // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
  5030. if (elm.length === undefined) {
  5031. // Simple all selector
  5032. if (selector === '*') {
  5033. return elm.nodeType == 1;
  5034. }
  5035. // Simple selector just elements
  5036. if (simpleSelectorRe.test(selector)) {
  5037. selector = selector.toLowerCase().split(/,/);
  5038. elm = elm.nodeName.toLowerCase();
  5039. for (i = selector.length - 1; i >= 0; i--) {
  5040. if (selector[i] == elm) {
  5041. return true;
  5042. }
  5043. }
  5044. return false;
  5045. }
  5046. }
  5047. // Is non element
  5048. if (elm.nodeType && elm.nodeType != 1) {
  5049. return false;
  5050. }
  5051. var elms = elm.nodeType ? [elm] : elm;
  5052. return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
  5053. },
  5054. // #endif
  5055. /**
  5056. * Adds the specified element to another element or elements.
  5057. *
  5058. * @method add
  5059. * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
  5060. * @param {String/Element} name Name of new element to add or existing element to add.
  5061. * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
  5062. * @param {String} html Optional inner HTML contents to add for each element.
  5063. * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
  5064. * were passed in.
  5065. * @example
  5066. * // Adds a new paragraph to the end of the active editor
  5067. * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
  5068. */
  5069. add: function(parentElm, name, attrs, html, create) {
  5070. var self = this;
  5071. return this.run(parentElm, function(parentElm) {
  5072. var newElm;
  5073. newElm = is(name, 'string') ? self.doc.createElement(name) : name;
  5074. self.setAttribs(newElm, attrs);
  5075. if (html) {
  5076. if (html.nodeType) {
  5077. newElm.appendChild(html);
  5078. } else {
  5079. self.setHTML(newElm, html);
  5080. }
  5081. }
  5082. return !create ? parentElm.appendChild(newElm) : newElm;
  5083. });
  5084. },
  5085. /**
  5086. * Creates a new element.
  5087. *
  5088. * @method create
  5089. * @param {String} name Name of new element.
  5090. * @param {Object} attrs Optional object name/value collection with element attributes.
  5091. * @param {String} html Optional HTML string to set as inner HTML of the element.
  5092. * @return {Element} HTML DOM node element that got created.
  5093. * @example
  5094. * // Adds an element where the caret/selection is in the active editor
  5095. * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
  5096. * tinymce.activeEditor.selection.setNode(el);
  5097. */
  5098. create: function(name, attrs, html) {
  5099. return this.add(this.doc.createElement(name), name, attrs, html, 1);
  5100. },
  5101. /**
  5102. * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
  5103. *
  5104. * @method createHTML
  5105. * @param {String} name Name of new element.
  5106. * @param {Object} attrs Optional object name/value collection with element attributes.
  5107. * @param {String} html Optional HTML string to set as inner HTML of the element.
  5108. * @return {String} String with new HTML element, for example: <a href="#">test</a>.
  5109. * @example
  5110. * // Creates a html chunk and inserts it at the current selection/caret location
  5111. * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
  5112. */
  5113. createHTML: function(name, attrs, html) {
  5114. var outHtml = '', key;
  5115. outHtml += '<' + name;
  5116. for (key in attrs) {
  5117. if (attrs.hasOwnProperty(key) && attrs[key] !== null) {
  5118. outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
  5119. }
  5120. }
  5121. // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
  5122. if (typeof(html) != "undefined") {
  5123. return outHtml + '>' + html + '</' + name + '>';
  5124. }
  5125. return outHtml + ' />';
  5126. },
  5127. /**
  5128. * Creates a document fragment out of the specified HTML string.
  5129. *
  5130. * @method createFragment
  5131. * @param {String} html Html string to create fragment from.
  5132. * @return {DocumentFragment} Document fragment node.
  5133. */
  5134. createFragment: function(html) {
  5135. var frag, node, doc = this.doc, container;
  5136. container = doc.createElement("div");
  5137. frag = doc.createDocumentFragment();
  5138. if (html) {
  5139. container.innerHTML = html;
  5140. }
  5141. while ((node = container.firstChild)) {
  5142. frag.appendChild(node);
  5143. }
  5144. return frag;
  5145. },
  5146. /**
  5147. * Removes/deletes the specified element(s) from the DOM.
  5148. *
  5149. * @method remove
  5150. * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
  5151. * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be
  5152. * placed at the location of the removed element.
  5153. * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
  5154. * were passed in.
  5155. * @example
  5156. * // Removes all paragraphs in the active editor
  5157. * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
  5158. *
  5159. * // Removes an element by id in the document
  5160. * tinymce.DOM.remove('mydiv');
  5161. */
  5162. remove: function(node, keep_children) {
  5163. return this.run(node, function(node) {
  5164. var child, parent = node.parentNode;
  5165. if (!parent) {
  5166. return null;
  5167. }
  5168. if (keep_children) {
  5169. while ((child = node.firstChild)) {
  5170. // IE 8 will crash if you don't remove completely empty text nodes
  5171. if (!isIE || child.nodeType !== 3 || child.nodeValue) {
  5172. parent.insertBefore(child, node);
  5173. } else {
  5174. node.removeChild(child);
  5175. }
  5176. }
  5177. }
  5178. return parent.removeChild(node);
  5179. });
  5180. },
  5181. /**
  5182. * Sets the CSS style value on a HTML element. The name can be a camelcase string
  5183. * or the CSS style name like background-color.
  5184. *
  5185. * @method setStyle
  5186. * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
  5187. * @param {String} na Name of the style value to set.
  5188. * @param {String} v Value to set on the style.
  5189. * @example
  5190. * // Sets a style value on all paragraphs in the currently active editor
  5191. * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
  5192. *
  5193. * // Sets a style value to an element by id in the current document
  5194. * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
  5195. */
  5196. setStyle: function(elm, name, value) {
  5197. return this.run(elm, function(elm) {
  5198. var self = this, style, key;
  5199. if (name) {
  5200. if (typeof(name) === 'string') {
  5201. style = elm.style;
  5202. // Camelcase it, if needed
  5203. name = name.replace(/-(\D)/g, function(a, b) {
  5204. return b.toUpperCase();
  5205. });
  5206. // Default px suffix on these
  5207. if (typeof(value) === 'number' && !numericCssMap[name]) {
  5208. value += 'px';
  5209. }
  5210. // IE specific opacity
  5211. if (name === "opacity" && elm.runtimeStyle && typeof(elm.runtimeStyle.opacity) === "undefined") {
  5212. style.filter = value === '' ? '' : "alpha(opacity=" + (value * 100) + ")";
  5213. }
  5214. if (name == "float") {
  5215. // Old IE vs modern browsers
  5216. name = "cssFloat" in elm.style ? "cssFloat" : "styleFloat";
  5217. }
  5218. try {
  5219. style[name] = value;
  5220. } catch (ex) {
  5221. // Ignore IE errors
  5222. }
  5223. // Force update of the style data
  5224. if (self.settings.update_styles) {
  5225. elm.removeAttribute('data-mce-style');
  5226. }
  5227. } else {
  5228. for (key in name) {
  5229. self.setStyle(elm, key, name[key]);
  5230. }
  5231. }
  5232. }
  5233. });
  5234. },
  5235. /**
  5236. * Returns the current style or runtime/computed value of an element.
  5237. *
  5238. * @method getStyle
  5239. * @param {String/Element} elm HTML element or element id string to get style from.
  5240. * @param {String} name Style name to return.
  5241. * @param {Boolean} computed Computed style.
  5242. * @return {String} Current style or computed style value of an element.
  5243. */
  5244. getStyle: function(elm, name, computed) {
  5245. elm = this.get(elm);
  5246. if (!elm) {
  5247. return;
  5248. }
  5249. // W3C
  5250. if (this.doc.defaultView && computed) {
  5251. // Remove camelcase
  5252. name = name.replace(/[A-Z]/g, function(a){
  5253. return '-' + a;
  5254. });
  5255. try {
  5256. return this.doc.defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  5257. } catch (ex) {
  5258. // Old safari might fail
  5259. return null;
  5260. }
  5261. }
  5262. // Camelcase it, if needed
  5263. name = name.replace(/-(\D)/g, function(a, b) {
  5264. return b.toUpperCase();
  5265. });
  5266. if (name == 'float') {
  5267. name = isIE ? 'styleFloat' : 'cssFloat';
  5268. }
  5269. // IE & Opera
  5270. if (elm.currentStyle && computed) {
  5271. return elm.currentStyle[name];
  5272. }
  5273. return elm.style ? elm.style[name] : undefined;
  5274. },
  5275. /**
  5276. * Sets multiple styles on the specified element(s).
  5277. *
  5278. * @method setStyles
  5279. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
  5280. * @param {Object} o Name/Value collection of style items to add to the element(s).
  5281. * @example
  5282. * // Sets styles on all paragraphs in the currently active editor
  5283. * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
  5284. *
  5285. * // Sets styles to an element by id in the current document
  5286. * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
  5287. */
  5288. setStyles: function(elm, styles) {
  5289. this.setStyle(elm, styles);
  5290. },
  5291. css: function(elm, name, value) {
  5292. this.setStyle(elm, name, value);
  5293. },
  5294. /**
  5295. * Removes all attributes from an element or elements.
  5296. *
  5297. * @method removeAllAttribs
  5298. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
  5299. */
  5300. removeAllAttribs: function(e) {
  5301. return this.run(e, function(e) {
  5302. var i, attrs = e.attributes;
  5303. for (i = attrs.length - 1; i >= 0; i--) {
  5304. e.removeAttributeNode(attrs.item(i));
  5305. }
  5306. });
  5307. },
  5308. /**
  5309. * Sets the specified attribute of an element or elements.
  5310. *
  5311. * @method setAttrib
  5312. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
  5313. * @param {String} n Name of attribute to set.
  5314. * @param {String} v Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove the attribute instead.
  5315. * @example
  5316. * // Sets class attribute on all paragraphs in the active editor
  5317. * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
  5318. *
  5319. * // Sets class attribute on a specific element in the current page
  5320. * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
  5321. */
  5322. setAttrib: function(e, n, v) {
  5323. var self = this;
  5324. // What's the point
  5325. if (!e || !n) {
  5326. return;
  5327. }
  5328. return this.run(e, function(e) {
  5329. var s = self.settings;
  5330. var originalValue = e.getAttribute(n);
  5331. if (v !== null) {
  5332. switch (n) {
  5333. case "style":
  5334. if (!is(v, 'string')) {
  5335. each(v, function(v, n) {
  5336. self.setStyle(e, n, v);
  5337. });
  5338. return;
  5339. }
  5340. // No mce_style for elements with these since they might get resized by the user
  5341. if (s.keep_values) {
  5342. if (v) {
  5343. e.setAttribute('data-mce-style', v, 2);
  5344. } else {
  5345. e.removeAttribute('data-mce-style', 2);
  5346. }
  5347. }
  5348. e.style.cssText = v;
  5349. break;
  5350. case "class":
  5351. e.className = v || ''; // Fix IE null bug
  5352. break;
  5353. case "src":
  5354. case "href":
  5355. if (s.keep_values) {
  5356. if (s.url_converter) {
  5357. v = s.url_converter.call(s.url_converter_scope || self, v, n, e);
  5358. }
  5359. self.setAttrib(e, 'data-mce-' + n, v, 2);
  5360. }
  5361. break;
  5362. case "shape":
  5363. e.setAttribute('data-mce-style', v);
  5364. break;
  5365. }
  5366. }
  5367. if (is(v) && v !== null && v.length !== 0) {
  5368. e.setAttribute(n, '' + v, 2);
  5369. } else {
  5370. e.removeAttribute(n, 2);
  5371. }
  5372. // fire onChangeAttrib event for attributes that have changed
  5373. if (originalValue != v && s.onSetAttrib) {
  5374. s.onSetAttrib({attrElm: e, attrName: n, attrValue: v});
  5375. }
  5376. });
  5377. },
  5378. /**
  5379. * Sets two or more specified attributes of an element or elements.
  5380. *
  5381. * @method setAttribs
  5382. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
  5383. * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
  5384. * @example
  5385. * // Sets class and title attributes on all paragraphs in the active editor
  5386. * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
  5387. *
  5388. * // Sets class and title attributes on a specific element in the current page
  5389. * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
  5390. */
  5391. setAttribs: function(elm, attrs) {
  5392. var self = this;
  5393. return this.run(elm, function(elm) {
  5394. each(attrs, function(value, name) {
  5395. self.setAttrib(elm, name, value);
  5396. });
  5397. });
  5398. },
  5399. /**
  5400. * Returns the specified attribute by name.
  5401. *
  5402. * @method getAttrib
  5403. * @param {String/Element} elm Element string id or DOM element to get attribute from.
  5404. * @param {String} name Name of attribute to get.
  5405. * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
  5406. * @return {String} Attribute value string, default value or null if the attribute wasn't found.
  5407. */
  5408. getAttrib: function(elm, name, defaultVal) {
  5409. var value, self = this, undef;
  5410. elm = self.get(elm);
  5411. if (!elm || elm.nodeType !== 1) {
  5412. return defaultVal === undef ? false : defaultVal;
  5413. }
  5414. if (!is(defaultVal)) {
  5415. defaultVal = '';
  5416. }
  5417. // Try the mce variant for these
  5418. if (/^(src|href|style|coords|shape)$/.test(name)) {
  5419. value = elm.getAttribute("data-mce-" + name);
  5420. if (value) {
  5421. return value;
  5422. }
  5423. }
  5424. if (isIE && self.props[name]) {
  5425. value = elm[self.props[name]];
  5426. value = value && value.nodeValue ? value.nodeValue : value;
  5427. }
  5428. if (!value) {
  5429. value = elm.getAttribute(name, 2);
  5430. }
  5431. // Check boolean attribs
  5432. if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(name)) {
  5433. if (elm[self.props[name]] === true && value === '') {
  5434. return name;
  5435. }
  5436. return value ? name : '';
  5437. }
  5438. // Inner input elements will override attributes on form elements
  5439. if (elm.nodeName === "FORM" && elm.getAttributeNode(name)) {
  5440. return elm.getAttributeNode(name).nodeValue;
  5441. }
  5442. if (name === 'style') {
  5443. value = value || elm.style.cssText;
  5444. if (value) {
  5445. value = self.serializeStyle(self.parseStyle(value), elm.nodeName);
  5446. if (self.settings.keep_values) {
  5447. elm.setAttribute('data-mce-style', value);
  5448. }
  5449. }
  5450. }
  5451. // Remove Apple and WebKit stuff
  5452. if (isWebKit && name === "class" && value) {
  5453. value = value.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
  5454. }
  5455. // Handle IE issues
  5456. if (isIE) {
  5457. switch (name) {
  5458. case 'rowspan':
  5459. case 'colspan':
  5460. // IE returns 1 as default value
  5461. if (value === 1) {
  5462. value = '';
  5463. }
  5464. break;
  5465. case 'size':
  5466. // IE returns +0 as default value for size
  5467. if (value === '+0' || value === 20 || value === 0) {
  5468. value = '';
  5469. }
  5470. break;
  5471. case 'width':
  5472. case 'height':
  5473. case 'vspace':
  5474. case 'checked':
  5475. case 'disabled':
  5476. case 'readonly':
  5477. if (value === 0) {
  5478. value = '';
  5479. }
  5480. break;
  5481. case 'hspace':
  5482. // IE returns -1 as default value
  5483. if (value === -1) {
  5484. value = '';
  5485. }
  5486. break;
  5487. case 'maxlength':
  5488. case 'tabindex':
  5489. // IE returns default value
  5490. if (value === 32768 || value === 2147483647 || value === '32768') {
  5491. value = '';
  5492. }
  5493. break;
  5494. case 'multiple':
  5495. case 'compact':
  5496. case 'noshade':
  5497. case 'nowrap':
  5498. if (value === 65535) {
  5499. return name;
  5500. }
  5501. return defaultVal;
  5502. case 'shape':
  5503. value = value.toLowerCase();
  5504. break;
  5505. default:
  5506. // IE has odd anonymous function for event attributes
  5507. if (name.indexOf('on') === 0 && value) {
  5508. value = ('' + value).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
  5509. }
  5510. }
  5511. }
  5512. return (value !== undef && value !== null && value !== '') ? '' + value : defaultVal;
  5513. },
  5514. /**
  5515. * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
  5516. *
  5517. * @method getPos
  5518. * @param {Element/String} elm HTML element or element id to get x, y position from.
  5519. * @param {Element} rootElm Optional root element to stop calculations at.
  5520. * @return {object} Absolute position of the specified element object with x, y fields.
  5521. */
  5522. getPos: function(elm, rootElm) {
  5523. var self = this, x = 0, y = 0, offsetParent, doc = self.doc, pos;
  5524. elm = self.get(elm);
  5525. rootElm = rootElm || doc.body;
  5526. if (elm) {
  5527. // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
  5528. if (rootElm === doc.body && elm.getBoundingClientRect) {
  5529. pos = elm.getBoundingClientRect();
  5530. rootElm = self.boxModel ? doc.documentElement : doc.body;
  5531. // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
  5532. // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
  5533. x = pos.left + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - rootElm.clientLeft;
  5534. y = pos.top + (doc.documentElement.scrollTop || doc.body.scrollTop) - rootElm.clientTop;
  5535. return {x: x, y: y};
  5536. }
  5537. offsetParent = elm;
  5538. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  5539. x += offsetParent.offsetLeft || 0;
  5540. y += offsetParent.offsetTop || 0;
  5541. offsetParent = offsetParent.offsetParent;
  5542. }
  5543. offsetParent = elm.parentNode;
  5544. while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
  5545. x -= offsetParent.scrollLeft || 0;
  5546. y -= offsetParent.scrollTop || 0;
  5547. offsetParent = offsetParent.parentNode;
  5548. }
  5549. }
  5550. return {x: x, y: y};
  5551. },
  5552. /**
  5553. * Parses the specified style value into an object collection. This parser will also
  5554. * merge and remove any redundant items that browsers might have added. It will also convert non-hex
  5555. * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
  5556. *
  5557. * @method parseStyle
  5558. * @param {String} cssText Style value to parse, for example: border:1px solid red;.
  5559. * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
  5560. */
  5561. parseStyle: function(cssText) {
  5562. return this.styles.parse(cssText);
  5563. },
  5564. /**
  5565. * Serializes the specified style object into a string.
  5566. *
  5567. * @method serializeStyle
  5568. * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
  5569. * @param {String} name Optional element name.
  5570. * @return {String} String representation of the style object, for example: border: 1px solid red.
  5571. */
  5572. serializeStyle: function(styles, name) {
  5573. return this.styles.serialize(styles, name);
  5574. },
  5575. /**
  5576. * Adds a style element at the top of the document with the specified cssText content.
  5577. *
  5578. * @method addStyle
  5579. * @param {String} cssText CSS Text style to add to top of head of document.
  5580. */
  5581. addStyle: function(cssText) {
  5582. var self = this, doc = self.doc, head, styleElm;
  5583. // Prevent inline from loading the same styles twice
  5584. if (self !== DOMUtils.DOM && doc === document) {
  5585. var addedStyles = DOMUtils.DOM.addedStyles;
  5586. addedStyles = addedStyles || [];
  5587. if (addedStyles[cssText]) {
  5588. return;
  5589. }
  5590. addedStyles[cssText] = true;
  5591. DOMUtils.DOM.addedStyles = addedStyles;
  5592. }
  5593. // Create style element if needed
  5594. styleElm = doc.getElementById('mceDefaultStyles');
  5595. if (!styleElm) {
  5596. styleElm = doc.createElement('style');
  5597. styleElm.id = 'mceDefaultStyles';
  5598. styleElm.type = 'text/css';
  5599. head = doc.getElementsByTagName('head')[0];
  5600. if (head.firstChild) {
  5601. head.insertBefore(styleElm, head.firstChild);
  5602. } else {
  5603. head.appendChild(styleElm);
  5604. }
  5605. }
  5606. // Append style data to old or new style element
  5607. if (styleElm.styleSheet) {
  5608. styleElm.styleSheet.cssText += cssText;
  5609. } else {
  5610. styleElm.appendChild(doc.createTextNode(cssText));
  5611. }
  5612. },
  5613. /**
  5614. * Imports/loads the specified CSS file into the document bound to the class.
  5615. *
  5616. * @method loadCSS
  5617. * @param {String} u URL to CSS file to load.
  5618. * @example
  5619. * // Loads a CSS file dynamically into the current document
  5620. * tinymce.DOM.loadCSS('somepath/some.css');
  5621. *
  5622. * // Loads a CSS file into the currently active editor instance
  5623. * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
  5624. *
  5625. * // Loads a CSS file into an editor instance by id
  5626. * tinymce.get('someid').dom.loadCSS('somepath/some.css');
  5627. *
  5628. * // Loads multiple CSS files into the current document
  5629. * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
  5630. */
  5631. loadCSS: function(url) {
  5632. var self = this, doc = self.doc, head;
  5633. // Prevent inline from loading the same CSS file twice
  5634. if (self !== DOMUtils.DOM && doc === document) {
  5635. DOMUtils.DOM.loadCSS(url);
  5636. return;
  5637. }
  5638. if (!url) {
  5639. url = '';
  5640. }
  5641. head = doc.getElementsByTagName('head')[0];
  5642. each(url.split(','), function(url) {
  5643. var link;
  5644. if (self.files[url]) {
  5645. return;
  5646. }
  5647. self.files[url] = true;
  5648. link = self.create('link', {rel: 'stylesheet', href: url});
  5649. // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
  5650. // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
  5651. // It's ugly but it seems to work fine.
  5652. if (isIE && doc.documentMode && doc.recalc) {
  5653. link.onload = function() {
  5654. if (doc.recalc) {
  5655. doc.recalc();
  5656. }
  5657. link.onload = null;
  5658. };
  5659. }
  5660. head.appendChild(link);
  5661. });
  5662. },
  5663. /**
  5664. * Adds a class to the specified element or elements.
  5665. *
  5666. * @method addClass
  5667. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  5668. * @param {String} cls Class name to add to each element.
  5669. * @return {String/Array} String with new class value or array with new class values for all elements.
  5670. * @example
  5671. * // Adds a class to all paragraphs in the active editor
  5672. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  5673. *
  5674. * // Adds a class to a specific element in the current page
  5675. * tinymce.DOM.addClass('mydiv', 'myclass');
  5676. */
  5677. addClass: function(elm, cls) {
  5678. return this.run(elm, function(elm) {
  5679. var clsVal;
  5680. if (!cls) {
  5681. return 0;
  5682. }
  5683. if (this.hasClass(elm, cls)) {
  5684. return elm.className;
  5685. }
  5686. clsVal = this.removeClass(elm, cls);
  5687. elm.className = clsVal = (clsVal !== '' ? (clsVal + ' ') : '') + cls;
  5688. return clsVal;
  5689. });
  5690. },
  5691. /**
  5692. * Removes a class from the specified element or elements.
  5693. *
  5694. * @method removeClass
  5695. * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
  5696. * @param {String} cls Class name to remove from each element.
  5697. * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
  5698. * were passed in.
  5699. * @example
  5700. * // Removes a class from all paragraphs in the active editor
  5701. * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
  5702. *
  5703. * // Removes a class from a specific element in the current page
  5704. * tinymce.DOM.removeClass('mydiv', 'myclass');
  5705. */
  5706. removeClass: function(elm, cls) {
  5707. var self = this, re;
  5708. return self.run(elm, function(elm) {
  5709. var val;
  5710. if (self.hasClass(elm, cls)) {
  5711. if (!re) {
  5712. re = new RegExp("(^|\\s+)" + cls + "(\\s+|$)", "g");
  5713. }
  5714. val = elm.className.replace(re, ' ');
  5715. val = trim(val != ' ' ? val : '');
  5716. elm.className = val;
  5717. // Empty class attr
  5718. if (!val) {
  5719. elm.removeAttribute('class');
  5720. elm.removeAttribute('className');
  5721. }
  5722. return val;
  5723. }
  5724. return elm.className;
  5725. });
  5726. },
  5727. /**
  5728. * Returns true if the specified element has the specified class.
  5729. *
  5730. * @method hasClass
  5731. * @param {String/Element} n HTML element or element id string to check CSS class on.
  5732. * @param {String} c CSS class to check for.
  5733. * @return {Boolean} true/false if the specified element has the specified class.
  5734. */
  5735. hasClass: function(elm, cls) {
  5736. elm = this.get(elm);
  5737. if (!elm || !cls) {
  5738. return false;
  5739. }
  5740. return (' ' + elm.className + ' ').indexOf(' ' + cls + ' ') !== -1;
  5741. },
  5742. /**
  5743. * Toggles the specified class on/off.
  5744. *
  5745. * @method toggleClass
  5746. * @param {Element} elm Element to toggle class on.
  5747. * @param {[type]} cls Class to toggle on/off.
  5748. * @param {[type]} state Optional state to set.
  5749. */
  5750. toggleClass: function(elm, cls, state) {
  5751. state = state === undefined ? !this.hasClass(elm, cls) : state;
  5752. if (this.hasClass(elm, cls) !== state) {
  5753. if (state) {
  5754. this.addClass(elm, cls);
  5755. } else {
  5756. this.removeClass(elm, cls);
  5757. }
  5758. }
  5759. },
  5760. /**
  5761. * Shows the specified element(s) by ID by setting the "display" style.
  5762. *
  5763. * @method show
  5764. * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
  5765. */
  5766. show: function(elm) {
  5767. return this.setStyle(elm, 'display', 'block');
  5768. },
  5769. /**
  5770. * Hides the specified element(s) by ID by setting the "display" style.
  5771. *
  5772. * @method hide
  5773. * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
  5774. * @example
  5775. * // Hides an element by id in the document
  5776. * tinymce.DOM.hide('myid');
  5777. */
  5778. hide: function(elm) {
  5779. return this.setStyle(elm, 'display', 'none');
  5780. },
  5781. /**
  5782. * Returns true/false if the element is hidden or not by checking the "display" style.
  5783. *
  5784. * @method isHidden
  5785. * @param {String/Element} e Id or element to check display state on.
  5786. * @return {Boolean} true/false if the element is hidden or not.
  5787. */
  5788. isHidden: function(elm) {
  5789. elm = this.get(elm);
  5790. return !elm || elm.style.display == 'none' || this.getStyle(elm, 'display') == 'none';
  5791. },
  5792. /**
  5793. * Returns a unique id. This can be useful when generating elements on the fly.
  5794. * This method will not check if the element already exists.
  5795. *
  5796. * @method uniqueId
  5797. * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
  5798. * @return {String} Unique id.
  5799. */
  5800. uniqueId: function(prefix) {
  5801. return (!prefix ? 'mce_' : prefix) + (this.counter++);
  5802. },
  5803. /**
  5804. * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
  5805. * URLs will get converted, hex color values fixed etc. Check processHTML for details.
  5806. *
  5807. * @method setHTML
  5808. * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside of.
  5809. * @param {String} h HTML content to set as inner HTML of the element.
  5810. * @example
  5811. * // Sets the inner HTML of all paragraphs in the active editor
  5812. * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
  5813. *
  5814. * // Sets the inner HTML of an element by id in the document
  5815. * tinymce.DOM.setHTML('mydiv', 'some inner html');
  5816. */
  5817. setHTML: function(element, html) {
  5818. var self = this;
  5819. return self.run(element, function(element) {
  5820. if (isIE) {
  5821. // Remove all child nodes, IE keeps empty text nodes in DOM
  5822. while (element.firstChild) {
  5823. element.removeChild(element.firstChild);
  5824. }
  5825. try {
  5826. // IE will remove comments from the beginning
  5827. // unless you padd the contents with something
  5828. element.innerHTML = '<br />' + html;
  5829. element.removeChild(element.firstChild);
  5830. } catch (ex) {
  5831. // IE sometimes produces an unknown runtime error on innerHTML if it's a block element
  5832. // within a block element for example a div inside a p
  5833. // This seems to fix this problem
  5834. // Create new div with HTML contents and a BR in front to keep comments
  5835. var newElement = self.create('div');
  5836. newElement.innerHTML = '<br />' + html;
  5837. // Add all children from div to target
  5838. each(grep(newElement.childNodes), function(node, i) {
  5839. // Skip br element
  5840. if (i && element.canHaveHTML) {
  5841. element.appendChild(node);
  5842. }
  5843. });
  5844. }
  5845. } else {
  5846. element.innerHTML = html;
  5847. }
  5848. return html;
  5849. });
  5850. },
  5851. /**
  5852. * Returns the outer HTML of an element.
  5853. *
  5854. * @method getOuterHTML
  5855. * @param {String/Element} elm Element ID or element object to get outer HTML from.
  5856. * @return {String} Outer HTML string.
  5857. * @example
  5858. * tinymce.DOM.getOuterHTML(editorElement);
  5859. * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
  5860. */
  5861. getOuterHTML: function(elm) {
  5862. var doc, self = this;
  5863. elm = self.get(elm);
  5864. if (!elm) {
  5865. return null;
  5866. }
  5867. if (elm.nodeType === 1 && self.hasOuterHTML) {
  5868. return elm.outerHTML;
  5869. }
  5870. doc = (elm.ownerDocument || self.doc).createElement("body");
  5871. doc.appendChild(elm.cloneNode(true));
  5872. return doc.innerHTML;
  5873. },
  5874. /**
  5875. * Sets the specified outer HTML on an element or elements.
  5876. *
  5877. * @method setOuterHTML
  5878. * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
  5879. * @param {Object} html HTML code to set as outer value for the element.
  5880. * @param {Document} doc Optional document scope to use in this process - defaults to the document of the DOM class.
  5881. * @example
  5882. * // Sets the outer HTML of all paragraphs in the active editor
  5883. * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
  5884. *
  5885. * // Sets the outer HTML of an element by id in the document
  5886. * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
  5887. */
  5888. setOuterHTML: function(elm, html, doc) {
  5889. var self = this;
  5890. return self.run(elm, function(elm) {
  5891. function set() {
  5892. var node, tempElm;
  5893. tempElm = doc.createElement("body");
  5894. tempElm.innerHTML = html;
  5895. node = tempElm.lastChild;
  5896. while (node) {
  5897. self.insertAfter(node.cloneNode(true), elm);
  5898. node = node.previousSibling;
  5899. }
  5900. self.remove(elm);
  5901. }
  5902. // Only set HTML on elements
  5903. if (elm.nodeType == 1) {
  5904. doc = doc || elm.ownerDocument || self.doc;
  5905. if (isIE) {
  5906. try {
  5907. // Try outerHTML for IE it sometimes produces an unknown runtime error
  5908. if (elm.nodeType == 1 && self.hasOuterHTML) {
  5909. elm.outerHTML = html;
  5910. } else {
  5911. set();
  5912. }
  5913. } catch (ex) {
  5914. // Fix for unknown runtime error
  5915. set();
  5916. }
  5917. } else {
  5918. set();
  5919. }
  5920. }
  5921. });
  5922. },
  5923. /**
  5924. * Entity decodes a string. This method decodes any HTML entities, such as &aring;.
  5925. *
  5926. * @method decode
  5927. * @param {String} s String to decode entities on.
  5928. * @return {String} Entity decoded string.
  5929. */
  5930. decode: Entities.decode,
  5931. /**
  5932. * Entity encodes a string. This method encodes the most common entities, such as <>"&.
  5933. *
  5934. * @method encode
  5935. * @param {String} text String to encode with entities.
  5936. * @return {String} Entity encoded string.
  5937. */
  5938. encode: Entities.encodeAllRaw,
  5939. /**
  5940. * Inserts an element after the reference element.
  5941. *
  5942. * @method insertAfter
  5943. * @param {Element} node Element to insert after the reference.
  5944. * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
  5945. * @return {Element/Array} Element that got added or an array with elements.
  5946. */
  5947. insertAfter: function(node, reference_node) {
  5948. reference_node = this.get(reference_node);
  5949. return this.run(node, function(node) {
  5950. var parent, nextSibling;
  5951. parent = reference_node.parentNode;
  5952. nextSibling = reference_node.nextSibling;
  5953. if (nextSibling) {
  5954. parent.insertBefore(node, nextSibling);
  5955. } else {
  5956. parent.appendChild(node);
  5957. }
  5958. return node;
  5959. });
  5960. },
  5961. /**
  5962. * Replaces the specified element or elements with the new element specified. The new element will
  5963. * be cloned if multiple input elements are passed in.
  5964. *
  5965. * @method replace
  5966. * @param {Element} newElm New element to replace old ones with.
  5967. * @param {Element/String/Array} oldELm Element DOM node, element id or array of elements or ids to replace.
  5968. * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
  5969. */
  5970. replace: function(newElm, oldElm, keepChildren) {
  5971. var self = this;
  5972. return self.run(oldElm, function(oldElm) {
  5973. if (is(oldElm, 'array')) {
  5974. newElm = newElm.cloneNode(true);
  5975. }
  5976. if (keepChildren) {
  5977. each(grep(oldElm.childNodes), function(node) {
  5978. newElm.appendChild(node);
  5979. });
  5980. }
  5981. return oldElm.parentNode.replaceChild(newElm, oldElm);
  5982. });
  5983. },
  5984. /**
  5985. * Renames the specified element and keeps its attributes and children.
  5986. *
  5987. * @method rename
  5988. * @param {Element} elm Element to rename.
  5989. * @param {String} name Name of the new element.
  5990. * @return {Element} New element or the old element if it needed renaming.
  5991. */
  5992. rename: function(elm, name) {
  5993. var self = this, newElm;
  5994. if (elm.nodeName != name.toUpperCase()) {
  5995. // Rename block element
  5996. newElm = self.create(name);
  5997. // Copy attribs to new block
  5998. each(self.getAttribs(elm), function(attr_node) {
  5999. self.setAttrib(newElm, attr_node.nodeName, self.getAttrib(elm, attr_node.nodeName));
  6000. });
  6001. // Replace block
  6002. self.replace(newElm, elm, 1);
  6003. }
  6004. return newElm || elm;
  6005. },
  6006. /**
  6007. * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
  6008. *
  6009. * @method findCommonAncestor
  6010. * @param {Element} a Element to find common ancestor of.
  6011. * @param {Element} b Element to find common ancestor of.
  6012. * @return {Element} Common ancestor element of the two input elements.
  6013. */
  6014. findCommonAncestor: function(a, b) {
  6015. var ps = a, pe;
  6016. while (ps) {
  6017. pe = b;
  6018. while (pe && ps != pe) {
  6019. pe = pe.parentNode;
  6020. }
  6021. if (ps == pe) {
  6022. break;
  6023. }
  6024. ps = ps.parentNode;
  6025. }
  6026. if (!ps && a.ownerDocument) {
  6027. return a.ownerDocument.documentElement;
  6028. }
  6029. return ps;
  6030. },
  6031. /**
  6032. * Parses the specified RGB color value and returns a hex version of that color.
  6033. *
  6034. * @method toHex
  6035. * @param {String} rgbVal RGB string value like rgb(1,2,3)
  6036. * @return {String} Hex version of that RGB value like #FF00FF.
  6037. */
  6038. toHex: function(rgbVal) {
  6039. return this.styles.toHex(Tools.trim(rgbVal));
  6040. },
  6041. /**
  6042. * Executes the specified function on the element by id or dom element node or array of elements/id.
  6043. *
  6044. * @method run
  6045. * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
  6046. * @param {function} f Function to execute for each item.
  6047. * @param {Object} s Optional scope to execute the function in.
  6048. * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
  6049. */
  6050. run: function(elm, func, scope) {
  6051. var self = this, result;
  6052. if (typeof(elm) === 'string') {
  6053. elm = self.get(elm);
  6054. }
  6055. if (!elm) {
  6056. return false;
  6057. }
  6058. scope = scope || this;
  6059. if (!elm.nodeType && (elm.length || elm.length === 0)) {
  6060. result = [];
  6061. each(elm, function(elm, i) {
  6062. if (elm) {
  6063. if (typeof(elm) == 'string') {
  6064. elm = self.get(elm);
  6065. }
  6066. result.push(func.call(scope, elm, i));
  6067. }
  6068. });
  6069. return result;
  6070. }
  6071. return func.call(scope, elm);
  6072. },
  6073. /**
  6074. * Returns a NodeList with attributes for the element.
  6075. *
  6076. * @method getAttribs
  6077. * @param {HTMLElement/string} elm Element node or string id to get attributes from.
  6078. * @return {NodeList} NodeList with attributes.
  6079. */
  6080. getAttribs: function(elm) {
  6081. var attrs;
  6082. elm = this.get(elm);
  6083. if (!elm) {
  6084. return [];
  6085. }
  6086. if (isIE) {
  6087. attrs = [];
  6088. // Object will throw exception in IE
  6089. if (elm.nodeName == 'OBJECT') {
  6090. return elm.attributes;
  6091. }
  6092. // IE doesn't keep the selected attribute if you clone option elements
  6093. if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
  6094. attrs.push({specified: 1, nodeName: 'selected'});
  6095. }
  6096. // It's crazy that this is faster in IE but it's because it returns all attributes all the time
  6097. var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
  6098. elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
  6099. attrs.push({specified: 1, nodeName: a});
  6100. });
  6101. return attrs;
  6102. }
  6103. return elm.attributes;
  6104. },
  6105. /**
  6106. * Returns true/false if the specified node is to be considered empty or not.
  6107. *
  6108. * @example
  6109. * tinymce.DOM.isEmpty(node, {img: true});
  6110. * @method isEmpty
  6111. * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
  6112. * @return {Boolean} true/false if the node is empty or not.
  6113. */
  6114. isEmpty: function(node, elements) {
  6115. var self = this, i, attributes, type, walker, name, brCount = 0;
  6116. node = node.firstChild;
  6117. if (node) {
  6118. walker = new TreeWalker(node, node.parentNode);
  6119. elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
  6120. do {
  6121. type = node.nodeType;
  6122. if (type === 1) {
  6123. // Ignore bogus elements
  6124. if (node.getAttribute('data-mce-bogus')) {
  6125. continue;
  6126. }
  6127. // Keep empty elements like <img />
  6128. name = node.nodeName.toLowerCase();
  6129. if (elements && elements[name]) {
  6130. // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
  6131. if (name === 'br') {
  6132. brCount++;
  6133. continue;
  6134. }
  6135. return false;
  6136. }
  6137. // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
  6138. attributes = self.getAttribs(node);
  6139. i = node.attributes.length;
  6140. while (i--) {
  6141. name = node.attributes[i].nodeName;
  6142. if (name === "name" || name === 'data-mce-bookmark') {
  6143. return false;
  6144. }
  6145. }
  6146. }
  6147. // Keep comment nodes
  6148. if (type == 8) {
  6149. return false;
  6150. }
  6151. // Keep non whitespace text nodes
  6152. if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
  6153. return false;
  6154. }
  6155. } while ((node = walker.next()));
  6156. }
  6157. return brCount <= 1;
  6158. },
  6159. /**
  6160. * Creates a new DOM Range object. This will use the native DOM Range API if it's
  6161. * available. If it's not, it will fall back to the custom TinyMCE implementation.
  6162. *
  6163. * @method createRng
  6164. * @return {DOMRange} DOM Range object.
  6165. * @example
  6166. * var rng = tinymce.DOM.createRng();
  6167. * alert(rng.startContainer + "," + rng.startOffset);
  6168. */
  6169. createRng: function() {
  6170. var doc = this.doc;
  6171. return doc.createRange ? doc.createRange() : new Range(this);
  6172. },
  6173. /**
  6174. * Returns the index of the specified node within its parent.
  6175. *
  6176. * @method nodeIndex
  6177. * @param {Node} node Node to look for.
  6178. * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
  6179. * @return {Number} Index of the specified node.
  6180. */
  6181. nodeIndex: function(node, normalized) {
  6182. var idx = 0, lastNodeType, nodeType;
  6183. if (node) {
  6184. for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
  6185. nodeType = node.nodeType;
  6186. // Normalize text nodes
  6187. if (normalized && nodeType == 3) {
  6188. if (nodeType == lastNodeType || !node.nodeValue.length) {
  6189. continue;
  6190. }
  6191. }
  6192. idx++;
  6193. lastNodeType = nodeType;
  6194. }
  6195. }
  6196. return idx;
  6197. },
  6198. /**
  6199. * Splits an element into two new elements and places the specified split
  6200. * element or elements between the new ones. For example splitting the paragraph at the bold element in
  6201. * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
  6202. *
  6203. * @method split
  6204. * @param {Element} parentElm Parent element to split.
  6205. * @param {Element} splitElm Element to split at.
  6206. * @param {Element} replacementElm Optional replacement element to replace the split element with.
  6207. * @return {Element} Returns the split element or the replacement element if that is specified.
  6208. */
  6209. split: function(parentElm, splitElm, replacementElm) {
  6210. var self = this, r = self.createRng(), bef, aft, pa;
  6211. // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
  6212. // but we don't want that in our code since it serves no purpose for the end user
  6213. // For example splitting this html at the bold element:
  6214. // <p>text 1<span><b>CHOP</b></span>text 2</p>
  6215. // would produce:
  6216. // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
  6217. // this function will then trim off empty edges and produce:
  6218. // <p>text 1</p><b>CHOP</b><p>text 2</p>
  6219. function trimNode(node) {
  6220. var i, children = node.childNodes, type = node.nodeType;
  6221. function surroundedBySpans(node) {
  6222. var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
  6223. var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
  6224. return previousIsSpan && nextIsSpan;
  6225. }
  6226. if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
  6227. return;
  6228. }
  6229. for (i = children.length - 1; i >= 0; i--) {
  6230. trimNode(children[i]);
  6231. }
  6232. if (type != 9) {
  6233. // Keep non whitespace text nodes
  6234. if (type == 3 && node.nodeValue.length > 0) {
  6235. // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
  6236. // Also keep text nodes with only spaces if surrounded by spans.
  6237. // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
  6238. var trimmedLength = trim(node.nodeValue).length;
  6239. if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
  6240. return;
  6241. }
  6242. } else if (type == 1) {
  6243. // If the only child is a bookmark then move it up
  6244. children = node.childNodes;
  6245. // TODO fix this complex if
  6246. if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
  6247. children[0].getAttribute('data-mce-type') == 'bookmark') {
  6248. node.parentNode.insertBefore(children[0], node);
  6249. }
  6250. // Keep non empty elements or img, hr etc
  6251. if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
  6252. return;
  6253. }
  6254. }
  6255. self.remove(node);
  6256. }
  6257. return node;
  6258. }
  6259. if (parentElm && splitElm) {
  6260. // Get before chunk
  6261. r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
  6262. r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
  6263. bef = r.extractContents();
  6264. // Get after chunk
  6265. r = self.createRng();
  6266. r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
  6267. r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
  6268. aft = r.extractContents();
  6269. // Insert before chunk
  6270. pa = parentElm.parentNode;
  6271. pa.insertBefore(trimNode(bef), parentElm);
  6272. // Insert middle chunk
  6273. if (replacementElm) {
  6274. pa.replaceChild(replacementElm, splitElm);
  6275. } else {
  6276. pa.insertBefore(splitElm, parentElm);
  6277. }
  6278. // Insert after chunk
  6279. pa.insertBefore(trimNode(aft), parentElm);
  6280. self.remove(parentElm);
  6281. return replacementElm || splitElm;
  6282. }
  6283. },
  6284. /**
  6285. * Adds an event handler to the specified object.
  6286. *
  6287. * @method bind
  6288. * @param {Element/Document/Window/Array} target Target element to bind events to.
  6289. * handler to or an array of elements/ids/documents.
  6290. * @param {String} name Name of event handler to add, for example: click.
  6291. * @param {function} func Function to execute when the event occurs.
  6292. * @param {Object} scope Optional scope to execute the function in.
  6293. * @return {function} Function callback handler the same as the one passed in.
  6294. */
  6295. bind: function(target, name, func, scope) {
  6296. var self = this;
  6297. if (Tools.isArray(target)) {
  6298. var i = target.length;
  6299. while (i--) {
  6300. target[i] = self.bind(target[i], name, func, scope);
  6301. }
  6302. return target;
  6303. }
  6304. // Collect all window/document events bound by editor instance
  6305. if (self.settings.collect && (target === self.doc || target === self.win)) {
  6306. self.boundEvents.push([target, name, func, scope]);
  6307. }
  6308. return self.events.bind(target, name, func, scope || self);
  6309. },
  6310. /**
  6311. * Removes the specified event handler by name and function from an element or collection of elements.
  6312. *
  6313. * @method unbind
  6314. * @param {Element/Document/Window/Array} target Target element to unbind events on.
  6315. * @param {String} name Event handler name, for example: "click"
  6316. * @param {function} func Function to remove.
  6317. * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
  6318. * were passed in.
  6319. */
  6320. unbind: function(target, name, func) {
  6321. var self = this, i;
  6322. if (Tools.isArray(target)) {
  6323. i = target.length;
  6324. while (i--) {
  6325. target[i] = self.unbind(target[i], name, func);
  6326. }
  6327. return target;
  6328. }
  6329. // Remove any bound events matching the input
  6330. if (self.boundEvents && (target === self.doc || target === self.win)) {
  6331. i = self.boundEvents.length;
  6332. while (i--) {
  6333. var item = self.boundEvents[i];
  6334. if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
  6335. this.events.unbind(item[0], item[1], item[2]);
  6336. }
  6337. }
  6338. }
  6339. return this.events.unbind(target, name, func);
  6340. },
  6341. /**
  6342. * Fires the specified event name with object on target.
  6343. *
  6344. * @method fire
  6345. * @param {Node/Document/Window} target Target element or object to fire event on.
  6346. * @param {String} name Name of the event to fire.
  6347. * @param {Object} evt Event object to send.
  6348. * @return {Event} Event object.
  6349. */
  6350. fire: function(target, name, evt) {
  6351. return this.events.fire(target, name, evt);
  6352. },
  6353. // Returns the content editable state of a node
  6354. getContentEditable: function(node) {
  6355. var contentEditable;
  6356. // Check type
  6357. if (node.nodeType != 1) {
  6358. return null;
  6359. }
  6360. // Check for fake content editable
  6361. contentEditable = node.getAttribute("data-mce-contenteditable");
  6362. if (contentEditable && contentEditable !== "inherit") {
  6363. return contentEditable;
  6364. }
  6365. // Check for real content editable
  6366. return node.contentEditable !== "inherit" ? node.contentEditable : null;
  6367. },
  6368. /**
  6369. * Destroys all internal references to the DOM to solve IE leak issues.
  6370. *
  6371. * @method destroy
  6372. */
  6373. destroy: function() {
  6374. var self = this;
  6375. // Unbind all events bound to window/document by editor instance
  6376. if (self.boundEvents) {
  6377. var i = self.boundEvents.length;
  6378. while (i--) {
  6379. var item = self.boundEvents[i];
  6380. this.events.unbind(item[0], item[1], item[2]);
  6381. }
  6382. self.boundEvents = null;
  6383. }
  6384. // Restore sizzle document to window.document
  6385. // Since the current document might be removed producing "Permission denied" on IE see #6325
  6386. if (Sizzle.setDocument) {
  6387. Sizzle.setDocument();
  6388. }
  6389. self.win = self.doc = self.root = self.events = self.frag = null;
  6390. },
  6391. // #ifdef debug
  6392. dumpRng: function(r) {
  6393. return (
  6394. 'startContainer: ' + r.startContainer.nodeName +
  6395. ', startOffset: ' + r.startOffset +
  6396. ', endContainer: ' + r.endContainer.nodeName +
  6397. ', endOffset: ' + r.endOffset
  6398. );
  6399. },
  6400. // #endif
  6401. _findSib: function(node, selector, name) {
  6402. var self = this, func = selector;
  6403. if (node) {
  6404. // If expression make a function of it using is
  6405. if (typeof(func) == 'string') {
  6406. func = function(node) {
  6407. return self.is(node, selector);
  6408. };
  6409. }
  6410. // Loop all siblings
  6411. for (node = node[name]; node; node = node[name]) {
  6412. if (func(node)) {
  6413. return node;
  6414. }
  6415. }
  6416. }
  6417. return null;
  6418. }
  6419. };
  6420. /**
  6421. * Instance of DOMUtils for the current document.
  6422. *
  6423. * @static
  6424. * @property DOM
  6425. * @type tinymce.dom.DOMUtils
  6426. * @example
  6427. * // Example of how to add a class to some element by id
  6428. * tinymce.DOM.addClass('someid', 'someclass');
  6429. */
  6430. DOMUtils.DOM = new DOMUtils(document);
  6431. return DOMUtils;
  6432. });
  6433. // Included from: js/tinymce/classes/dom/ScriptLoader.js
  6434. /**
  6435. * ScriptLoader.js
  6436. *
  6437. * Copyright, Moxiecode Systems AB
  6438. * Released under LGPL License.
  6439. *
  6440. * License: http://www.tinymce.com/license
  6441. * Contributing: http://www.tinymce.com/contributing
  6442. */
  6443. /*globals console*/
  6444. /**
  6445. * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
  6446. * when various items gets loaded. This class is useful to load external JavaScript files.
  6447. *
  6448. * @class tinymce.dom.ScriptLoader
  6449. * @example
  6450. * // Load a script from a specific URL using the global script loader
  6451. * tinymce.ScriptLoader.load('somescript.js');
  6452. *
  6453. * // Load a script using a unique instance of the script loader
  6454. * var scriptLoader = new tinymce.dom.ScriptLoader();
  6455. *
  6456. * scriptLoader.load('somescript.js');
  6457. *
  6458. * // Load multiple scripts
  6459. * var scriptLoader = new tinymce.dom.ScriptLoader();
  6460. *
  6461. * scriptLoader.add('somescript1.js');
  6462. * scriptLoader.add('somescript2.js');
  6463. * scriptLoader.add('somescript3.js');
  6464. *
  6465. * scriptLoader.loadQueue(function() {
  6466. * alert('All scripts are now loaded.');
  6467. * });
  6468. */
  6469. define("tinymce/dom/ScriptLoader", [
  6470. "tinymce/dom/DOMUtils",
  6471. "tinymce/util/Tools"
  6472. ], function(DOMUtils, Tools) {
  6473. var DOM = DOMUtils.DOM;
  6474. var each = Tools.each, grep = Tools.grep;
  6475. function ScriptLoader() {
  6476. var QUEUED = 0,
  6477. LOADING = 1,
  6478. LOADED = 2,
  6479. states = {},
  6480. queue = [],
  6481. scriptLoadedCallbacks = {},
  6482. queueLoadedCallbacks = [],
  6483. loading = 0,
  6484. undef;
  6485. /**
  6486. * Loads a specific script directly without adding it to the load queue.
  6487. *
  6488. * @method load
  6489. * @param {String} url Absolute URL to script to add.
  6490. * @param {function} callback Optional callback function to execute ones this script gets loaded.
  6491. * @param {Object} scope Optional scope to execute callback in.
  6492. */
  6493. function loadScript(url, callback) {
  6494. var dom = DOM, elm, id;
  6495. // Execute callback when script is loaded
  6496. function done() {
  6497. dom.remove(id);
  6498. if (elm) {
  6499. elm.onreadystatechange = elm.onload = elm = null;
  6500. }
  6501. callback();
  6502. }
  6503. function error() {
  6504. /*eslint no-console:0 */
  6505. // Report the error so it's easier for people to spot loading errors
  6506. if (typeof(console) !== "undefined" && console.log) {
  6507. console.log("Failed to load: " + url);
  6508. }
  6509. // We can't mark it as done if there is a load error since
  6510. // A) We don't want to produce 404 errors on the server and
  6511. // B) the onerror event won't fire on all browsers.
  6512. // done();
  6513. }
  6514. id = dom.uniqueId();
  6515. // Create new script element
  6516. elm = document.createElement('script');
  6517. elm.id = id;
  6518. elm.type = 'text/javascript';
  6519. elm.src = url;
  6520. // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
  6521. if ("onreadystatechange" in elm) {
  6522. elm.onreadystatechange = function() {
  6523. if (/loaded|complete/.test(elm.readyState)) {
  6524. done();
  6525. }
  6526. };
  6527. } else {
  6528. elm.onload = done;
  6529. }
  6530. // Add onerror event will get fired on some browsers but not all of them
  6531. elm.onerror = error;
  6532. // Add script to document
  6533. (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
  6534. }
  6535. /**
  6536. * Returns true/false if a script has been loaded or not.
  6537. *
  6538. * @method isDone
  6539. * @param {String} url URL to check for.
  6540. * @return {Boolean} true/false if the URL is loaded.
  6541. */
  6542. this.isDone = function(url) {
  6543. return states[url] == LOADED;
  6544. };
  6545. /**
  6546. * Marks a specific script to be loaded. This can be useful if a script got loaded outside
  6547. * the script loader or to skip it from loading some script.
  6548. *
  6549. * @method markDone
  6550. * @param {string} u Absolute URL to the script to mark as loaded.
  6551. */
  6552. this.markDone = function(url) {
  6553. states[url] = LOADED;
  6554. };
  6555. /**
  6556. * Adds a specific script to the load queue of the script loader.
  6557. *
  6558. * @method add
  6559. * @param {String} url Absolute URL to script to add.
  6560. * @param {function} callback Optional callback function to execute ones this script gets loaded.
  6561. * @param {Object} scope Optional scope to execute callback in.
  6562. */
  6563. this.add = this.load = function(url, callback, scope) {
  6564. var state = states[url];
  6565. // Add url to load queue
  6566. if (state == undef) {
  6567. queue.push(url);
  6568. states[url] = QUEUED;
  6569. }
  6570. if (callback) {
  6571. // Store away callback for later execution
  6572. if (!scriptLoadedCallbacks[url]) {
  6573. scriptLoadedCallbacks[url] = [];
  6574. }
  6575. scriptLoadedCallbacks[url].push({
  6576. func: callback,
  6577. scope: scope || this
  6578. });
  6579. }
  6580. };
  6581. /**
  6582. * Starts the loading of the queue.
  6583. *
  6584. * @method loadQueue
  6585. * @param {function} callback Optional callback to execute when all queued items are loaded.
  6586. * @param {Object} scope Optional scope to execute the callback in.
  6587. */
  6588. this.loadQueue = function(callback, scope) {
  6589. this.loadScripts(queue, callback, scope);
  6590. };
  6591. /**
  6592. * Loads the specified queue of files and executes the callback ones they are loaded.
  6593. * This method is generally not used outside this class but it might be useful in some scenarios.
  6594. *
  6595. * @method loadScripts
  6596. * @param {Array} scripts Array of queue items to load.
  6597. * @param {function} callback Optional callback to execute ones all items are loaded.
  6598. * @param {Object} scope Optional scope to execute callback in.
  6599. */
  6600. this.loadScripts = function(scripts, callback, scope) {
  6601. var loadScripts;
  6602. function execScriptLoadedCallbacks(url) {
  6603. // Execute URL callback functions
  6604. each(scriptLoadedCallbacks[url], function(callback) {
  6605. callback.func.call(callback.scope);
  6606. });
  6607. scriptLoadedCallbacks[url] = undef;
  6608. }
  6609. queueLoadedCallbacks.push({
  6610. func: callback,
  6611. scope: scope || this
  6612. });
  6613. loadScripts = function() {
  6614. var loadingScripts = grep(scripts);
  6615. // Current scripts has been handled
  6616. scripts.length = 0;
  6617. // Load scripts that needs to be loaded
  6618. each(loadingScripts, function(url) {
  6619. // Script is already loaded then execute script callbacks directly
  6620. if (states[url] == LOADED) {
  6621. execScriptLoadedCallbacks(url);
  6622. return;
  6623. }
  6624. // Is script not loading then start loading it
  6625. if (states[url] != LOADING) {
  6626. states[url] = LOADING;
  6627. loading++;
  6628. loadScript(url, function() {
  6629. states[url] = LOADED;
  6630. loading--;
  6631. execScriptLoadedCallbacks(url);
  6632. // Load more scripts if they where added by the recently loaded script
  6633. loadScripts();
  6634. });
  6635. }
  6636. });
  6637. // No scripts are currently loading then execute all pending queue loaded callbacks
  6638. if (!loading) {
  6639. each(queueLoadedCallbacks, function(callback) {
  6640. callback.func.call(callback.scope);
  6641. });
  6642. queueLoadedCallbacks.length = 0;
  6643. }
  6644. };
  6645. loadScripts();
  6646. };
  6647. }
  6648. ScriptLoader.ScriptLoader = new ScriptLoader();
  6649. return ScriptLoader;
  6650. });
  6651. // Included from: js/tinymce/classes/AddOnManager.js
  6652. /**
  6653. * AddOnManager.js
  6654. *
  6655. * Copyright, Moxiecode Systems AB
  6656. * Released under LGPL License.
  6657. *
  6658. * License: http://www.tinymce.com/license
  6659. * Contributing: http://www.tinymce.com/contributing
  6660. */
  6661. /**
  6662. * This class handles the loading of themes/plugins or other add-ons and their language packs.
  6663. *
  6664. * @class tinymce.AddOnManager
  6665. */
  6666. define("tinymce/AddOnManager", [
  6667. "tinymce/dom/ScriptLoader",
  6668. "tinymce/util/Tools"
  6669. ], function(ScriptLoader, Tools) {
  6670. var each = Tools.each;
  6671. function AddOnManager() {
  6672. var self = this;
  6673. self.items = [];
  6674. self.urls = {};
  6675. self.lookup = {};
  6676. }
  6677. AddOnManager.prototype = {
  6678. /**
  6679. * Returns the specified add on by the short name.
  6680. *
  6681. * @method get
  6682. * @param {String} name Add-on to look for.
  6683. * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
  6684. */
  6685. get: function(name) {
  6686. if (this.lookup[name]) {
  6687. return this.lookup[name].instance;
  6688. } else {
  6689. return undefined;
  6690. }
  6691. },
  6692. dependencies: function(name) {
  6693. var result;
  6694. if (this.lookup[name]) {
  6695. result = this.lookup[name].dependencies;
  6696. }
  6697. return result || [];
  6698. },
  6699. /**
  6700. * Loads a language pack for the specified add-on.
  6701. *
  6702. * @method requireLangPack
  6703. * @param {String} name Short name of the add-on.
  6704. * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
  6705. */
  6706. requireLangPack: function(name, languages) {
  6707. if (AddOnManager.language && AddOnManager.languageLoad !== false) {
  6708. if (languages && new RegExp('([, ]|\\b)' + AddOnManager.language + '([, ]|\\b)').test(languages) === false) {
  6709. return;
  6710. }
  6711. ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + AddOnManager.language + '.js');
  6712. }
  6713. },
  6714. /**
  6715. * Adds a instance of the add-on by it's short name.
  6716. *
  6717. * @method add
  6718. * @param {String} id Short name/id for the add-on.
  6719. * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
  6720. * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
  6721. * @example
  6722. * // Create a simple plugin
  6723. * tinymce.create('tinymce.plugins.TestPlugin', {
  6724. * TestPlugin: function(ed, url) {
  6725. * ed.on('click', function(e) {
  6726. * ed.windowManager.alert('Hello World!');
  6727. * });
  6728. * }
  6729. * });
  6730. *
  6731. * // Register plugin using the add method
  6732. * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
  6733. *
  6734. * // Initialize TinyMCE
  6735. * tinymce.init({
  6736. * ...
  6737. * plugins: '-test' // Init the plugin but don't try to load it
  6738. * });
  6739. */
  6740. add: function(id, addOn, dependencies) {
  6741. this.items.push(addOn);
  6742. this.lookup[id] = {instance: addOn, dependencies: dependencies};
  6743. return addOn;
  6744. },
  6745. createUrl: function(baseUrl, dep) {
  6746. if (typeof dep === "object") {
  6747. return dep;
  6748. } else {
  6749. return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
  6750. }
  6751. },
  6752. /**
  6753. * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
  6754. * This should be used in development mode. A new compressor/javascript munger process will ensure that the
  6755. * components are put together into the plugin.js file and compressed correctly.
  6756. *
  6757. * @method addComponents
  6758. * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
  6759. * @param {Array} scripts Array containing the names of the scripts to load.
  6760. */
  6761. addComponents: function(pluginName, scripts) {
  6762. var pluginUrl = this.urls[pluginName];
  6763. each(scripts, function(script) {
  6764. ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
  6765. });
  6766. },
  6767. /**
  6768. * Loads an add-on from a specific url.
  6769. *
  6770. * @method load
  6771. * @param {String} name Short name of the add-on that gets loaded.
  6772. * @param {String} addOnUrl URL to the add-on that will get loaded.
  6773. * @param {function} callback Optional callback to execute ones the add-on is loaded.
  6774. * @param {Object} scope Optional scope to execute the callback in.
  6775. * @example
  6776. * // Loads a plugin from an external URL
  6777. * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
  6778. *
  6779. * // Initialize TinyMCE
  6780. * tinymce.init({
  6781. * ...
  6782. * plugins: '-myplugin' // Don't try to load it again
  6783. * });
  6784. */
  6785. load: function(name, addOnUrl, callback, scope) {
  6786. var self = this, url = addOnUrl;
  6787. function loadDependencies() {
  6788. var dependencies = self.dependencies(name);
  6789. each(dependencies, function(dep) {
  6790. var newUrl = self.createUrl(addOnUrl, dep);
  6791. self.load(newUrl.resource, newUrl, undefined, undefined);
  6792. });
  6793. if (callback) {
  6794. if (scope) {
  6795. callback.call(scope);
  6796. } else {
  6797. callback.call(ScriptLoader);
  6798. }
  6799. }
  6800. }
  6801. if (self.urls[name]) {
  6802. return;
  6803. }
  6804. if (typeof addOnUrl === "object") {
  6805. url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
  6806. }
  6807. if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
  6808. url = AddOnManager.baseURL + '/' + url;
  6809. }
  6810. self.urls[name] = url.substring(0, url.lastIndexOf('/'));
  6811. if (self.lookup[name]) {
  6812. loadDependencies();
  6813. } else {
  6814. ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
  6815. }
  6816. }
  6817. };
  6818. AddOnManager.PluginManager = new AddOnManager();
  6819. AddOnManager.ThemeManager = new AddOnManager();
  6820. return AddOnManager;
  6821. });
  6822. /**
  6823. * TinyMCE theme class.
  6824. *
  6825. * @class tinymce.Theme
  6826. */
  6827. /**
  6828. * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
  6829. *
  6830. * @method renderUI
  6831. * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
  6832. * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
  6833. */
  6834. /**
  6835. * 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.
  6836. *
  6837. * @class tinymce.Plugin
  6838. * @example
  6839. * tinymce.PluginManager.add('example', function(editor, url) {
  6840. * // Add a button that opens a window
  6841. * editor.addButton('example', {
  6842. * text: 'My button',
  6843. * icon: false,
  6844. * onclick: function() {
  6845. * // Open window
  6846. * editor.windowManager.open({
  6847. * title: 'Example plugin',
  6848. * body: [
  6849. * {type: 'textbox', name: 'title', label: 'Title'}
  6850. * ],
  6851. * onsubmit: function(e) {
  6852. * // Insert content when the window form is submitted
  6853. * editor.insertContent('Title: ' + e.data.title);
  6854. * }
  6855. * });
  6856. * }
  6857. * });
  6858. *
  6859. * // Adds a menu item to the tools menu
  6860. * editor.addMenuItem('example', {
  6861. * text: 'Example plugin',
  6862. * context: 'tools',
  6863. * onclick: function() {
  6864. * // Open window with a specific url
  6865. * editor.windowManager.open({
  6866. * title: 'TinyMCE site',
  6867. * url: 'http://www.tinymce.com',
  6868. * width: 800,
  6869. * height: 600,
  6870. * buttons: [{
  6871. * text: 'Close',
  6872. * onclick: 'close'
  6873. * }]
  6874. * });
  6875. * }
  6876. * });
  6877. * });
  6878. */
  6879. // Included from: js/tinymce/classes/html/Node.js
  6880. /**
  6881. * Node.js
  6882. *
  6883. * Copyright, Moxiecode Systems AB
  6884. * Released under LGPL License.
  6885. *
  6886. * License: http://www.tinymce.com/license
  6887. * Contributing: http://www.tinymce.com/contributing
  6888. */
  6889. /**
  6890. * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
  6891. *
  6892. * @example
  6893. * var node = new tinymce.html.Node('strong', 1);
  6894. * someRoot.append(node);
  6895. *
  6896. * @class tinymce.html.Node
  6897. * @version 3.4
  6898. */
  6899. define("tinymce/html/Node", [], function() {
  6900. var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
  6901. '#text': 3,
  6902. '#comment': 8,
  6903. '#cdata': 4,
  6904. '#pi': 7,
  6905. '#doctype': 10,
  6906. '#document-fragment': 11
  6907. };
  6908. // Walks the tree left/right
  6909. function walk(node, root_node, prev) {
  6910. var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
  6911. // Walk into nodes if it has a start
  6912. if (node[startName]) {
  6913. return node[startName];
  6914. }
  6915. // Return the sibling if it has one
  6916. if (node !== root_node) {
  6917. sibling = node[siblingName];
  6918. if (sibling) {
  6919. return sibling;
  6920. }
  6921. // Walk up the parents to look for siblings
  6922. for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
  6923. sibling = parent[siblingName];
  6924. if (sibling) {
  6925. return sibling;
  6926. }
  6927. }
  6928. }
  6929. }
  6930. /**
  6931. * Constructs a new Node instance.
  6932. *
  6933. * @constructor
  6934. * @method Node
  6935. * @param {String} name Name of the node type.
  6936. * @param {Number} type Numeric type representing the node.
  6937. */
  6938. function Node(name, type) {
  6939. this.name = name;
  6940. this.type = type;
  6941. if (type === 1) {
  6942. this.attributes = [];
  6943. this.attributes.map = {};
  6944. }
  6945. }
  6946. Node.prototype = {
  6947. /**
  6948. * Replaces the current node with the specified one.
  6949. *
  6950. * @example
  6951. * someNode.replace(someNewNode);
  6952. *
  6953. * @method replace
  6954. * @param {tinymce.html.Node} node Node to replace the current node with.
  6955. * @return {tinymce.html.Node} The old node that got replaced.
  6956. */
  6957. replace: function(node) {
  6958. var self = this;
  6959. if (node.parent) {
  6960. node.remove();
  6961. }
  6962. self.insert(node, self);
  6963. self.remove();
  6964. return self;
  6965. },
  6966. /**
  6967. * Gets/sets or removes an attribute by name.
  6968. *
  6969. * @example
  6970. * someNode.attr("name", "value"); // Sets an attribute
  6971. * console.log(someNode.attr("name")); // Gets an attribute
  6972. * someNode.attr("name", null); // Removes an attribute
  6973. *
  6974. * @method attr
  6975. * @param {String} name Attribute name to set or get.
  6976. * @param {String} value Optional value to set.
  6977. * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
  6978. */
  6979. attr: function(name, value) {
  6980. var self = this, attrs, i, undef;
  6981. if (typeof name !== "string") {
  6982. for (i in name) {
  6983. self.attr(i, name[i]);
  6984. }
  6985. return self;
  6986. }
  6987. if ((attrs = self.attributes)) {
  6988. if (value !== undef) {
  6989. // Remove attribute
  6990. if (value === null) {
  6991. if (name in attrs.map) {
  6992. delete attrs.map[name];
  6993. i = attrs.length;
  6994. while (i--) {
  6995. if (attrs[i].name === name) {
  6996. attrs = attrs.splice(i, 1);
  6997. return self;
  6998. }
  6999. }
  7000. }
  7001. return self;
  7002. }
  7003. // Set attribute
  7004. if (name in attrs.map) {
  7005. // Set attribute
  7006. i = attrs.length;
  7007. while (i--) {
  7008. if (attrs[i].name === name) {
  7009. attrs[i].value = value;
  7010. break;
  7011. }
  7012. }
  7013. } else {
  7014. attrs.push({name: name, value: value});
  7015. }
  7016. attrs.map[name] = value;
  7017. return self;
  7018. } else {
  7019. return attrs.map[name];
  7020. }
  7021. }
  7022. },
  7023. /**
  7024. * Does a shallow clones the node into a new node. It will also exclude id attributes since
  7025. * there should only be one id per document.
  7026. *
  7027. * @example
  7028. * var clonedNode = node.clone();
  7029. *
  7030. * @method clone
  7031. * @return {tinymce.html.Node} New copy of the original node.
  7032. */
  7033. clone: function() {
  7034. var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
  7035. // Clone element attributes
  7036. if ((selfAttrs = self.attributes)) {
  7037. cloneAttrs = [];
  7038. cloneAttrs.map = {};
  7039. for (i = 0, l = selfAttrs.length; i < l; i++) {
  7040. selfAttr = selfAttrs[i];
  7041. // Clone everything except id
  7042. if (selfAttr.name !== 'id') {
  7043. cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
  7044. cloneAttrs.map[selfAttr.name] = selfAttr.value;
  7045. }
  7046. }
  7047. clone.attributes = cloneAttrs;
  7048. }
  7049. clone.value = self.value;
  7050. clone.shortEnded = self.shortEnded;
  7051. return clone;
  7052. },
  7053. /**
  7054. * Wraps the node in in another node.
  7055. *
  7056. * @example
  7057. * node.wrap(wrapperNode);
  7058. *
  7059. * @method wrap
  7060. */
  7061. wrap: function(wrapper) {
  7062. var self = this;
  7063. self.parent.insert(wrapper, self);
  7064. wrapper.append(self);
  7065. return self;
  7066. },
  7067. /**
  7068. * Unwraps the node in other words it removes the node but keeps the children.
  7069. *
  7070. * @example
  7071. * node.unwrap();
  7072. *
  7073. * @method unwrap
  7074. */
  7075. unwrap: function() {
  7076. var self = this, node, next;
  7077. for (node = self.firstChild; node; ) {
  7078. next = node.next;
  7079. self.insert(node, self, true);
  7080. node = next;
  7081. }
  7082. self.remove();
  7083. },
  7084. /**
  7085. * Removes the node from it's parent.
  7086. *
  7087. * @example
  7088. * node.remove();
  7089. *
  7090. * @method remove
  7091. * @return {tinymce.html.Node} Current node that got removed.
  7092. */
  7093. remove: function() {
  7094. var self = this, parent = self.parent, next = self.next, prev = self.prev;
  7095. if (parent) {
  7096. if (parent.firstChild === self) {
  7097. parent.firstChild = next;
  7098. if (next) {
  7099. next.prev = null;
  7100. }
  7101. } else {
  7102. prev.next = next;
  7103. }
  7104. if (parent.lastChild === self) {
  7105. parent.lastChild = prev;
  7106. if (prev) {
  7107. prev.next = null;
  7108. }
  7109. } else {
  7110. next.prev = prev;
  7111. }
  7112. self.parent = self.next = self.prev = null;
  7113. }
  7114. return self;
  7115. },
  7116. /**
  7117. * Appends a new node as a child of the current node.
  7118. *
  7119. * @example
  7120. * node.append(someNode);
  7121. *
  7122. * @method append
  7123. * @param {tinymce.html.Node} node Node to append as a child of the current one.
  7124. * @return {tinymce.html.Node} The node that got appended.
  7125. */
  7126. append: function(node) {
  7127. var self = this, last;
  7128. if (node.parent) {
  7129. node.remove();
  7130. }
  7131. last = self.lastChild;
  7132. if (last) {
  7133. last.next = node;
  7134. node.prev = last;
  7135. self.lastChild = node;
  7136. } else {
  7137. self.lastChild = self.firstChild = node;
  7138. }
  7139. node.parent = self;
  7140. return node;
  7141. },
  7142. /**
  7143. * Inserts a node at a specific position as a child of the current node.
  7144. *
  7145. * @example
  7146. * parentNode.insert(newChildNode, oldChildNode);
  7147. *
  7148. * @method insert
  7149. * @param {tinymce.html.Node} node Node to insert as a child of the current node.
  7150. * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
  7151. * @param {Boolean} before Optional state to insert the node before the reference node.
  7152. * @return {tinymce.html.Node} The node that got inserted.
  7153. */
  7154. insert: function(node, ref_node, before) {
  7155. var parent;
  7156. if (node.parent) {
  7157. node.remove();
  7158. }
  7159. parent = ref_node.parent || this;
  7160. if (before) {
  7161. if (ref_node === parent.firstChild) {
  7162. parent.firstChild = node;
  7163. } else {
  7164. ref_node.prev.next = node;
  7165. }
  7166. node.prev = ref_node.prev;
  7167. node.next = ref_node;
  7168. ref_node.prev = node;
  7169. } else {
  7170. if (ref_node === parent.lastChild) {
  7171. parent.lastChild = node;
  7172. } else {
  7173. ref_node.next.prev = node;
  7174. }
  7175. node.next = ref_node.next;
  7176. node.prev = ref_node;
  7177. ref_node.next = node;
  7178. }
  7179. node.parent = parent;
  7180. return node;
  7181. },
  7182. /**
  7183. * Get all children by name.
  7184. *
  7185. * @method getAll
  7186. * @param {String} name Name of the child nodes to collect.
  7187. * @return {Array} Array with child nodes matchin the specified name.
  7188. */
  7189. getAll: function(name) {
  7190. var self = this, node, collection = [];
  7191. for (node = self.firstChild; node; node = walk(node, self)) {
  7192. if (node.name === name) {
  7193. collection.push(node);
  7194. }
  7195. }
  7196. return collection;
  7197. },
  7198. /**
  7199. * Removes all children of the current node.
  7200. *
  7201. * @method empty
  7202. * @return {tinymce.html.Node} The current node that got cleared.
  7203. */
  7204. empty: function() {
  7205. var self = this, nodes, i, node;
  7206. // Remove all children
  7207. if (self.firstChild) {
  7208. nodes = [];
  7209. // Collect the children
  7210. for (node = self.firstChild; node; node = walk(node, self)) {
  7211. nodes.push(node);
  7212. }
  7213. // Remove the children
  7214. i = nodes.length;
  7215. while (i--) {
  7216. node = nodes[i];
  7217. node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
  7218. }
  7219. }
  7220. self.firstChild = self.lastChild = null;
  7221. return self;
  7222. },
  7223. /**
  7224. * Returns true/false if the node is to be considered empty or not.
  7225. *
  7226. * @example
  7227. * node.isEmpty({img: true});
  7228. * @method isEmpty
  7229. * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
  7230. * @return {Boolean} true/false if the node is empty or not.
  7231. */
  7232. isEmpty: function(elements) {
  7233. var self = this, node = self.firstChild, i, name;
  7234. if (node) {
  7235. do {
  7236. if (node.type === 1) {
  7237. // Ignore bogus elements
  7238. if (node.attributes.map['data-mce-bogus']) {
  7239. continue;
  7240. }
  7241. // Keep empty elements like <img />
  7242. if (elements[node.name]) {
  7243. return false;
  7244. }
  7245. // Keep elements with data attributes or name attribute like <a name="1"></a>
  7246. i = node.attributes.length;
  7247. while (i--) {
  7248. name = node.attributes[i].name;
  7249. if (name === "name" || name.indexOf('data-mce-') === 0) {
  7250. return false;
  7251. }
  7252. }
  7253. }
  7254. // Keep comments
  7255. if (node.type === 8) {
  7256. return false;
  7257. }
  7258. // Keep non whitespace text nodes
  7259. if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
  7260. return false;
  7261. }
  7262. } while ((node = walk(node, self)));
  7263. }
  7264. return true;
  7265. },
  7266. /**
  7267. * Walks to the next or previous node and returns that node or null if it wasn't found.
  7268. *
  7269. * @method walk
  7270. * @param {Boolean} prev Optional previous node state defaults to false.
  7271. * @return {tinymce.html.Node} Node that is next to or previous of the current node.
  7272. */
  7273. walk: function(prev) {
  7274. return walk(this, null, prev);
  7275. }
  7276. };
  7277. /**
  7278. * Creates a node of a specific type.
  7279. *
  7280. * @static
  7281. * @method create
  7282. * @param {String} name Name of the node type to create for example "b" or "#text".
  7283. * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
  7284. */
  7285. Node.create = function(name, attrs) {
  7286. var node, attrName;
  7287. // Create node
  7288. node = new Node(name, typeLookup[name] || 1);
  7289. // Add attributes if needed
  7290. if (attrs) {
  7291. for (attrName in attrs) {
  7292. node.attr(attrName, attrs[attrName]);
  7293. }
  7294. }
  7295. return node;
  7296. };
  7297. return Node;
  7298. });
  7299. // Included from: js/tinymce/classes/html/Schema.js
  7300. /**
  7301. * Schema.js
  7302. *
  7303. * Copyright, Moxiecode Systems AB
  7304. * Released under LGPL License.
  7305. *
  7306. * License: http://www.tinymce.com/license
  7307. * Contributing: http://www.tinymce.com/contributing
  7308. */
  7309. /**
  7310. * Schema validator class.
  7311. *
  7312. * @class tinymce.html.Schema
  7313. * @example
  7314. * if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
  7315. * alert('span is valid child of p.');
  7316. *
  7317. * if (tinymce.activeEditor.schema.getElementRule('p'))
  7318. * alert('P is a valid element.');
  7319. *
  7320. * @class tinymce.html.Schema
  7321. * @version 3.4
  7322. */
  7323. define("tinymce/html/Schema", [
  7324. "tinymce/util/Tools"
  7325. ], function(Tools) {
  7326. var mapCache = {};
  7327. var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
  7328. function split(items, delim) {
  7329. return items ? items.split(delim || ' ') : [];
  7330. }
  7331. /**
  7332. * Builds a schema lookup table
  7333. *
  7334. * @private
  7335. * @param {String} type html4, html5 or html5-strict schema type.
  7336. * @return {Object} Schema lookup table.
  7337. */
  7338. function compileSchema(type) {
  7339. var schema = {}, globalAttributes, blockContent;
  7340. var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
  7341. function add(name, attributes, children) {
  7342. var ni, i, attributesOrder, args = arguments;
  7343. function arrayToMap(array) {
  7344. var map = {}, i, l;
  7345. for (i = 0, l = array.length; i < l; i++) {
  7346. map[array[i]] = {};
  7347. }
  7348. return map;
  7349. }
  7350. children = children || [];
  7351. attributes = attributes || "";
  7352. if (typeof(children) === "string") {
  7353. children = split(children);
  7354. }
  7355. // Split string children
  7356. for (i = 3; i < args.length; i++) {
  7357. if (typeof(args[i]) === "string") {
  7358. args[i] = split(args[i]);
  7359. }
  7360. children.push.apply(children, args[i]);
  7361. }
  7362. name = split(name);
  7363. ni = name.length;
  7364. while (ni--) {
  7365. attributesOrder = [].concat(globalAttributes, split(attributes));
  7366. schema[name[ni]] = {
  7367. attributes: arrayToMap(attributesOrder),
  7368. attributesOrder: attributesOrder,
  7369. children: arrayToMap(children)
  7370. };
  7371. }
  7372. }
  7373. function addAttrs(name, attributes) {
  7374. var ni, schemaItem, i, l;
  7375. name = split(name);
  7376. ni = name.length;
  7377. attributes = split(attributes);
  7378. while (ni--) {
  7379. schemaItem = schema[name[ni]];
  7380. for (i = 0, l = attributes.length; i < l; i++) {
  7381. schemaItem.attributes[attributes[i]] = {};
  7382. schemaItem.attributesOrder.push(attributes[i]);
  7383. }
  7384. }
  7385. }
  7386. // Use cached schema
  7387. if (mapCache[type]) {
  7388. return mapCache[type];
  7389. }
  7390. // Attributes present on all elements
  7391. globalAttributes = split("id accesskey class dir lang style tabindex title");
  7392. // Event attributes can be opt-in/opt-out
  7393. /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
  7394. "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
  7395. "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
  7396. "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
  7397. "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
  7398. "onwaiting"
  7399. );*/
  7400. // Block content elements
  7401. blockContent = split(
  7402. "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
  7403. );
  7404. // Phrasing content elements from the HTML5 spec (inline)
  7405. phrasingContent = split(
  7406. "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
  7407. "label map noscript object q s samp script select small span strong sub sup " +
  7408. "textarea u var #text #comment"
  7409. );
  7410. // Add HTML5 items to globalAttributes, blockContent, phrasingContent
  7411. if (type != "html4") {
  7412. globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
  7413. "hidden spellcheck translate"));
  7414. blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
  7415. phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output progress time wbr " +
  7416. "video ruby bdi keygen"));
  7417. }
  7418. // Add HTML4 elements unless it's html5-strict
  7419. if (type != "html5-strict") {
  7420. globalAttributes.push("xml:lang");
  7421. html4PhrasingContent = split("acronym applet basefont big font strike tt");
  7422. phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
  7423. each(html4PhrasingContent, function(name) {
  7424. add(name, "", phrasingContent);
  7425. });
  7426. html4BlockContent = split("center dir isindex noframes");
  7427. blockContent.push.apply(blockContent, html4BlockContent);
  7428. // Flow content elements from the HTML5 spec (block+inline)
  7429. flowContent = [].concat(blockContent, phrasingContent);
  7430. each(html4BlockContent, function(name) {
  7431. add(name, "", flowContent);
  7432. });
  7433. }
  7434. // Flow content elements from the HTML5 spec (block+inline)
  7435. flowContent = flowContent || [].concat(blockContent, phrasingContent);
  7436. // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
  7437. // Schema items <element name>, <specific attributes>, <children ..>
  7438. add("html", "manifest", "head body");
  7439. add("head", "", "base command link meta noscript script style title");
  7440. add("title hr noscript br");
  7441. add("base", "href target");
  7442. add("link", "href rel media hreflang type sizes hreflang");
  7443. add("meta", "name http-equiv content charset");
  7444. add("style", "media type scoped");
  7445. add("script", "src async defer type charset");
  7446. add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
  7447. "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
  7448. "onpopstate onresize onscroll onstorage onunload", flowContent);
  7449. add("address dt dd div caption", "", flowContent);
  7450. 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);
  7451. add("blockquote", "cite", flowContent);
  7452. add("ol", "reversed start type", "li");
  7453. add("ul", "", "li");
  7454. add("li", "value", flowContent);
  7455. add("dl", "", "dt dd");
  7456. add("a", "href target rel media hreflang type", phrasingContent);
  7457. add("q", "cite", phrasingContent);
  7458. add("ins del", "cite datetime", flowContent);
  7459. add("img", "src alt usemap ismap width height");
  7460. add("iframe", "src name width height", flowContent);
  7461. add("embed", "src type width height");
  7462. add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
  7463. add("param", "name value");
  7464. add("map", "name", flowContent, "area");
  7465. add("area", "alt coords shape href target rel media hreflang type");
  7466. add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
  7467. add("colgroup", "span", "col");
  7468. add("col", "span");
  7469. add("tbody thead tfoot", "", "tr");
  7470. add("tr", "", "td th");
  7471. add("td", "colspan rowspan headers", flowContent);
  7472. add("th", "colspan rowspan headers scope abbr", flowContent);
  7473. add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
  7474. add("fieldset", "disabled form name", flowContent, "legend");
  7475. add("label", "form for", phrasingContent);
  7476. add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
  7477. "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
  7478. );
  7479. add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
  7480. type == "html4" ? flowContent : phrasingContent);
  7481. add("select", "disabled form multiple name required size", "option optgroup");
  7482. add("optgroup", "disabled label", "option");
  7483. add("option", "disabled label selected value");
  7484. add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
  7485. add("menu", "type label", flowContent, "li");
  7486. add("noscript", "", flowContent);
  7487. // Extend with HTML5 elements
  7488. if (type != "html4") {
  7489. add("wbr");
  7490. add("ruby", "", phrasingContent, "rt rp");
  7491. add("figcaption", "", flowContent);
  7492. add("mark rt rp summary bdi", "", phrasingContent);
  7493. add("canvas", "width height", flowContent);
  7494. add("video", "src crossorigin poster preload autoplay mediagroup loop " +
  7495. "muted controls width height", flowContent, "track source");
  7496. add("audio", "src crossorigin preload autoplay mediagroup loop muted controls", flowContent, "track source");
  7497. add("source", "src type media");
  7498. add("track", "kind src srclang label default");
  7499. add("datalist", "", phrasingContent, "option");
  7500. add("article section nav aside header footer", "", flowContent);
  7501. add("hgroup", "", "h1 h2 h3 h4 h5 h6");
  7502. add("figure", "", flowContent, "figcaption");
  7503. add("time", "datetime", phrasingContent);
  7504. add("dialog", "open", flowContent);
  7505. add("command", "type label icon disabled checked radiogroup command");
  7506. add("output", "for form name", phrasingContent);
  7507. add("progress", "value max", phrasingContent);
  7508. add("meter", "value min max low high optimum", phrasingContent);
  7509. add("details", "open", flowContent, "summary");
  7510. add("keygen", "autofocus challenge disabled form keytype name");
  7511. }
  7512. // Extend with HTML4 attributes unless it's html5-strict
  7513. if (type != "html5-strict") {
  7514. addAttrs("script", "language xml:space");
  7515. addAttrs("style", "xml:space");
  7516. addAttrs("object", "declare classid codebase codetype archive standby align border hspace vspace");
  7517. addAttrs("param", "valuetype type");
  7518. addAttrs("a", "charset name rev shape coords");
  7519. addAttrs("br", "clear");
  7520. addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
  7521. addAttrs("img", "name longdesc align border hspace vspace");
  7522. addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
  7523. addAttrs("font basefont", "size color face");
  7524. addAttrs("input", "usemap align");
  7525. addAttrs("select", "onchange");
  7526. addAttrs("textarea");
  7527. addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
  7528. addAttrs("ul", "type compact");
  7529. addAttrs("li", "type");
  7530. addAttrs("ol dl menu dir", "compact");
  7531. addAttrs("pre", "width xml:space");
  7532. addAttrs("hr", "align noshade size width");
  7533. addAttrs("isindex", "prompt");
  7534. addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
  7535. addAttrs("col", "width align char charoff valign");
  7536. addAttrs("colgroup", "width align char charoff valign");
  7537. addAttrs("thead", "align char charoff valign");
  7538. addAttrs("tr", "align char charoff valign bgcolor");
  7539. addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
  7540. addAttrs("form", "accept");
  7541. addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
  7542. addAttrs("tfoot", "align char charoff valign");
  7543. addAttrs("tbody", "align char charoff valign");
  7544. addAttrs("area", "nohref");
  7545. addAttrs("body", "background bgcolor text link vlink alink");
  7546. }
  7547. // Extend with HTML5 attributes unless it's html4
  7548. if (type != "html4") {
  7549. addAttrs("input button select textarea", "autofocus");
  7550. addAttrs("input textarea", "placeholder");
  7551. addAttrs("a", "download");
  7552. addAttrs("link script img", "crossorigin");
  7553. addAttrs("iframe", "srcdoc sandbox seamless allowfullscreen");
  7554. }
  7555. // Special: iframe, ruby, video, audio, label
  7556. // Delete children of the same name from it's parent
  7557. // For example: form can't have a child of the name form
  7558. each(split('a form meter progress dfn'), function(name) {
  7559. if (schema[name]) {
  7560. delete schema[name].children[name];
  7561. }
  7562. });
  7563. // Delete header, footer, sectioning and heading content descendants
  7564. /*each('dt th address', function(name) {
  7565. delete schema[name].children[name];
  7566. });*/
  7567. // Caption can't have tables
  7568. delete schema.caption.children.table;
  7569. // TODO: LI:s can only have value if parent is OL
  7570. // TODO: Handle transparent elements
  7571. // a ins del canvas map
  7572. mapCache[type] = schema;
  7573. return schema;
  7574. }
  7575. /**
  7576. * Constructs a new Schema instance.
  7577. *
  7578. * @constructor
  7579. * @method Schema
  7580. * @param {Object} settings Name/value settings object.
  7581. */
  7582. return function(settings) {
  7583. var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
  7584. var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap;
  7585. var blockElementsMap, nonEmptyElementsMap, textBlockElementsMap, customElementsMap = {}, specialElements = {};
  7586. // Creates an lookup table map object for the specified option or the default value
  7587. function createLookupTable(option, default_value, extendWith) {
  7588. var value = settings[option];
  7589. if (!value) {
  7590. // Get cached default map or make it if needed
  7591. value = mapCache[option];
  7592. if (!value) {
  7593. value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
  7594. value = extend(value, extendWith);
  7595. mapCache[option] = value;
  7596. }
  7597. } else {
  7598. // Create custom map
  7599. value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
  7600. }
  7601. return value;
  7602. }
  7603. settings = settings || {};
  7604. schemaItems = compileSchema(settings.schema);
  7605. // Allow all elements and attributes if verify_html is set to false
  7606. if (settings.verify_html === false) {
  7607. settings.valid_elements = '*[*]';
  7608. }
  7609. // Build styles list
  7610. if (settings.valid_styles) {
  7611. validStyles = {};
  7612. // Convert styles into a rule list
  7613. each(settings.valid_styles, function(value, key) {
  7614. validStyles[key] = explode(value);
  7615. });
  7616. }
  7617. // Setup map objects
  7618. whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
  7619. selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
  7620. shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
  7621. 'meta param embed source wbr track');
  7622. boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
  7623. 'noshade nowrap readonly selected autoplay loop controls');
  7624. nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
  7625. textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
  7626. 'blockquote center dir fieldset header footer article section hgroup aside nav figure');
  7627. blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
  7628. 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
  7629. 'datalist select optgroup', textBlockElementsMap);
  7630. each((settings.special || 'script noscript style textarea').split(' '), function(name) {
  7631. specialElements[name] = new RegExp('<\/' + name + '[^>]*>','gi');
  7632. });
  7633. // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
  7634. function patternToRegExp(str) {
  7635. return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
  7636. }
  7637. // Parses the specified valid_elements string and adds to the current rules
  7638. // This function is a bit hard to read since it's heavily optimized for speed
  7639. function addValidElements(valid_elements) {
  7640. var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
  7641. prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
  7642. elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
  7643. attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
  7644. hasPatternsRegExp = /[*?+]/;
  7645. if (valid_elements) {
  7646. // Split valid elements into an array with rules
  7647. valid_elements = split(valid_elements, ',');
  7648. if (elements['@']) {
  7649. globalAttributes = elements['@'].attributes;
  7650. globalAttributesOrder = elements['@'].attributesOrder;
  7651. }
  7652. // Loop all rules
  7653. for (ei = 0, el = valid_elements.length; ei < el; ei++) {
  7654. // Parse element rule
  7655. matches = elementRuleRegExp.exec(valid_elements[ei]);
  7656. if (matches) {
  7657. // Setup local names for matches
  7658. prefix = matches[1];
  7659. elementName = matches[2];
  7660. outputName = matches[3];
  7661. attrData = matches[5];
  7662. // Create new attributes and attributesOrder
  7663. attributes = {};
  7664. attributesOrder = [];
  7665. // Create the new element
  7666. element = {
  7667. attributes: attributes,
  7668. attributesOrder: attributesOrder
  7669. };
  7670. // Padd empty elements prefix
  7671. if (prefix === '#') {
  7672. element.paddEmpty = true;
  7673. }
  7674. // Remove empty elements prefix
  7675. if (prefix === '-') {
  7676. element.removeEmpty = true;
  7677. }
  7678. if (matches[4] === '!') {
  7679. element.removeEmptyAttrs = true;
  7680. }
  7681. // Copy attributes from global rule into current rule
  7682. if (globalAttributes) {
  7683. for (key in globalAttributes) {
  7684. attributes[key] = globalAttributes[key];
  7685. }
  7686. attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
  7687. }
  7688. // Attributes defined
  7689. if (attrData) {
  7690. attrData = split(attrData, '|');
  7691. for (ai = 0, al = attrData.length; ai < al; ai++) {
  7692. matches = attrRuleRegExp.exec(attrData[ai]);
  7693. if (matches) {
  7694. attr = {};
  7695. attrType = matches[1];
  7696. attrName = matches[2].replace(/::/g, ':');
  7697. prefix = matches[3];
  7698. value = matches[4];
  7699. // Required
  7700. if (attrType === '!') {
  7701. element.attributesRequired = element.attributesRequired || [];
  7702. element.attributesRequired.push(attrName);
  7703. attr.required = true;
  7704. }
  7705. // Denied from global
  7706. if (attrType === '-') {
  7707. delete attributes[attrName];
  7708. attributesOrder.splice(inArray(attributesOrder, attrName), 1);
  7709. continue;
  7710. }
  7711. // Default value
  7712. if (prefix) {
  7713. // Default value
  7714. if (prefix === '=') {
  7715. element.attributesDefault = element.attributesDefault || [];
  7716. element.attributesDefault.push({name: attrName, value: value});
  7717. attr.defaultValue = value;
  7718. }
  7719. // Forced value
  7720. if (prefix === ':') {
  7721. element.attributesForced = element.attributesForced || [];
  7722. element.attributesForced.push({name: attrName, value: value});
  7723. attr.forcedValue = value;
  7724. }
  7725. // Required values
  7726. if (prefix === '<') {
  7727. attr.validValues = makeMap(value, '?');
  7728. }
  7729. }
  7730. // Check for attribute patterns
  7731. if (hasPatternsRegExp.test(attrName)) {
  7732. element.attributePatterns = element.attributePatterns || [];
  7733. attr.pattern = patternToRegExp(attrName);
  7734. element.attributePatterns.push(attr);
  7735. } else {
  7736. // Add attribute to order list if it doesn't already exist
  7737. if (!attributes[attrName]) {
  7738. attributesOrder.push(attrName);
  7739. }
  7740. attributes[attrName] = attr;
  7741. }
  7742. }
  7743. }
  7744. }
  7745. // Global rule, store away these for later usage
  7746. if (!globalAttributes && elementName == '@') {
  7747. globalAttributes = attributes;
  7748. globalAttributesOrder = attributesOrder;
  7749. }
  7750. // Handle substitute elements such as b/strong
  7751. if (outputName) {
  7752. element.outputName = elementName;
  7753. elements[outputName] = element;
  7754. }
  7755. // Add pattern or exact element
  7756. if (hasPatternsRegExp.test(elementName)) {
  7757. element.pattern = patternToRegExp(elementName);
  7758. patternElements.push(element);
  7759. } else {
  7760. elements[elementName] = element;
  7761. }
  7762. }
  7763. }
  7764. }
  7765. }
  7766. function setValidElements(valid_elements) {
  7767. elements = {};
  7768. patternElements = [];
  7769. addValidElements(valid_elements);
  7770. each(schemaItems, function(element, name) {
  7771. children[name] = element.children;
  7772. });
  7773. }
  7774. // Adds custom non HTML elements to the schema
  7775. function addCustomElements(custom_elements) {
  7776. var customElementRegExp = /^(~)?(.+)$/;
  7777. if (custom_elements) {
  7778. // Flush cached items since we are altering the default maps
  7779. mapCache.text_block_elements = mapCache.block_elements = null;
  7780. each(split(custom_elements, ','), function(rule) {
  7781. var matches = customElementRegExp.exec(rule),
  7782. inline = matches[1] === '~',
  7783. cloneName = inline ? 'span' : 'div',
  7784. name = matches[2];
  7785. children[name] = children[cloneName];
  7786. customElementsMap[name] = cloneName;
  7787. // If it's not marked as inline then add it to valid block elements
  7788. if (!inline) {
  7789. blockElementsMap[name.toUpperCase()] = {};
  7790. blockElementsMap[name] = {};
  7791. }
  7792. // Add elements clone if needed
  7793. if (!elements[name]) {
  7794. var customRule = elements[cloneName];
  7795. customRule = extend({}, customRule);
  7796. delete customRule.removeEmptyAttrs;
  7797. delete customRule.removeEmpty;
  7798. elements[name] = customRule;
  7799. }
  7800. // Add custom elements at span/div positions
  7801. each(children, function(element, elmName) {
  7802. if (element[cloneName]) {
  7803. children[elmName] = element = extend({}, children[elmName]);
  7804. element[name] = element[cloneName];
  7805. }
  7806. });
  7807. });
  7808. }
  7809. }
  7810. // Adds valid children to the schema object
  7811. function addValidChildren(valid_children) {
  7812. var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
  7813. if (valid_children) {
  7814. each(split(valid_children, ','), function(rule) {
  7815. var matches = childRuleRegExp.exec(rule), parent, prefix;
  7816. if (matches) {
  7817. prefix = matches[1];
  7818. // Add/remove items from default
  7819. if (prefix) {
  7820. parent = children[matches[2]];
  7821. } else {
  7822. parent = children[matches[2]] = {'#comment': {}};
  7823. }
  7824. parent = children[matches[2]];
  7825. each(split(matches[3], '|'), function(child) {
  7826. if (prefix === '-') {
  7827. // Clone the element before we delete
  7828. // things in it to not mess up default schemas
  7829. children[matches[2]] = parent = extend({}, children[matches[2]]);
  7830. delete parent[child];
  7831. } else {
  7832. parent[child] = {};
  7833. }
  7834. });
  7835. }
  7836. });
  7837. }
  7838. }
  7839. function getElementRule(name) {
  7840. var element = elements[name], i;
  7841. // Exact match found
  7842. if (element) {
  7843. return element;
  7844. }
  7845. // No exact match then try the patterns
  7846. i = patternElements.length;
  7847. while (i--) {
  7848. element = patternElements[i];
  7849. if (element.pattern.test(name)) {
  7850. return element;
  7851. }
  7852. }
  7853. }
  7854. if (!settings.valid_elements) {
  7855. // No valid elements defined then clone the elements from the schema spec
  7856. each(schemaItems, function(element, name) {
  7857. elements[name] = {
  7858. attributes: element.attributes,
  7859. attributesOrder: element.attributesOrder
  7860. };
  7861. children[name] = element.children;
  7862. });
  7863. // Switch these on HTML4
  7864. if (settings.schema != "html5") {
  7865. each(split('strong/b em/i'), function(item) {
  7866. item = split(item, '/');
  7867. elements[item[1]].outputName = item[0];
  7868. });
  7869. }
  7870. // Add default alt attribute for images
  7871. elements.img.attributesDefault = [{name: 'alt', value: ''}];
  7872. // Remove these if they are empty by default
  7873. each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
  7874. if (elements[name]) {
  7875. elements[name].removeEmpty = true;
  7876. }
  7877. });
  7878. // Padd these by default
  7879. each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
  7880. elements[name].paddEmpty = true;
  7881. });
  7882. // Remove these if they have no attributes
  7883. each(split('span'), function(name) {
  7884. elements[name].removeEmptyAttrs = true;
  7885. });
  7886. // Remove these by default
  7887. // TODO: Reenable in 4.1
  7888. /*each(split('script style'), function(name) {
  7889. delete elements[name];
  7890. });*/
  7891. } else {
  7892. setValidElements(settings.valid_elements);
  7893. }
  7894. addCustomElements(settings.custom_elements);
  7895. addValidChildren(settings.valid_children);
  7896. addValidElements(settings.extended_valid_elements);
  7897. // Todo: Remove this when we fix list handling to be valid
  7898. addValidChildren('+ol[ul|ol],+ul[ul|ol]');
  7899. // Delete invalid elements
  7900. if (settings.invalid_elements) {
  7901. each(explode(settings.invalid_elements), function(item) {
  7902. if (elements[item]) {
  7903. delete elements[item];
  7904. }
  7905. });
  7906. }
  7907. // If the user didn't allow span only allow internal spans
  7908. if (!getElementRule('span')) {
  7909. addValidElements('span[!data-mce-type|*]');
  7910. }
  7911. /**
  7912. * Name/value map object with valid parents and children to those parents.
  7913. *
  7914. * @example
  7915. * children = {
  7916. * div:{p:{}, h1:{}}
  7917. * };
  7918. * @field children
  7919. * @type Object
  7920. */
  7921. self.children = children;
  7922. /**
  7923. * Name/value map object with valid styles for each element.
  7924. *
  7925. * @field styles
  7926. * @type Object
  7927. */
  7928. self.styles = validStyles;
  7929. /**
  7930. * Returns a map with boolean attributes.
  7931. *
  7932. * @method getBoolAttrs
  7933. * @return {Object} Name/value lookup map for boolean attributes.
  7934. */
  7935. self.getBoolAttrs = function() {
  7936. return boolAttrMap;
  7937. };
  7938. /**
  7939. * Returns a map with block elements.
  7940. *
  7941. * @method getBlockElements
  7942. * @return {Object} Name/value lookup map for block elements.
  7943. */
  7944. self.getBlockElements = function() {
  7945. return blockElementsMap;
  7946. };
  7947. /**
  7948. * Returns a map with text block elements. Such as: p,h1-h6,div,address
  7949. *
  7950. * @method getTextBlockElements
  7951. * @return {Object} Name/value lookup map for block elements.
  7952. */
  7953. self.getTextBlockElements = function() {
  7954. return textBlockElementsMap;
  7955. };
  7956. /**
  7957. * Returns a map with short ended elements such as BR or IMG.
  7958. *
  7959. * @method getShortEndedElements
  7960. * @return {Object} Name/value lookup map for short ended elements.
  7961. */
  7962. self.getShortEndedElements = function() {
  7963. return shortEndedElementsMap;
  7964. };
  7965. /**
  7966. * Returns a map with self closing tags such as <li>.
  7967. *
  7968. * @method getSelfClosingElements
  7969. * @return {Object} Name/value lookup map for self closing tags elements.
  7970. */
  7971. self.getSelfClosingElements = function() {
  7972. return selfClosingElementsMap;
  7973. };
  7974. /**
  7975. * Returns a map with elements that should be treated as contents regardless if it has text
  7976. * content in them or not such as TD, VIDEO or IMG.
  7977. *
  7978. * @method getNonEmptyElements
  7979. * @return {Object} Name/value lookup map for non empty elements.
  7980. */
  7981. self.getNonEmptyElements = function() {
  7982. return nonEmptyElementsMap;
  7983. };
  7984. /**
  7985. * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
  7986. *
  7987. * @method getWhiteSpaceElements
  7988. * @return {Object} Name/value lookup map for white space elements.
  7989. */
  7990. self.getWhiteSpaceElements = function() {
  7991. return whiteSpaceElementsMap;
  7992. };
  7993. /**
  7994. * Returns a map with special elements. These are elements that needs to be parsed
  7995. * in a special way such as script, style, textarea etc. The map object values
  7996. * are regexps used to find the end of the element.
  7997. *
  7998. * @method getSpecialElements
  7999. * @return {Object} Name/value lookup map for special elements.
  8000. */
  8001. self.getSpecialElements = function() {
  8002. return specialElements;
  8003. };
  8004. /**
  8005. * Returns true/false if the specified element and it's child is valid or not
  8006. * according to the schema.
  8007. *
  8008. * @method isValidChild
  8009. * @param {String} name Element name to check for.
  8010. * @param {String} child Element child to verify.
  8011. * @return {Boolean} True/false if the element is a valid child of the specified parent.
  8012. */
  8013. self.isValidChild = function(name, child) {
  8014. var parent = children[name];
  8015. return !!(parent && parent[child]);
  8016. };
  8017. /**
  8018. * Returns true/false if the specified element name and optional attribute is
  8019. * valid according to the schema.
  8020. *
  8021. * @method isValid
  8022. * @param {String} name Name of element to check.
  8023. * @param {String} attr Optional attribute name to check for.
  8024. * @return {Boolean} True/false if the element and attribute is valid.
  8025. */
  8026. self.isValid = function(name, attr) {
  8027. var attrPatterns, i, rule = getElementRule(name);
  8028. // Check if it's a valid element
  8029. if (rule) {
  8030. if (attr) {
  8031. // Check if attribute name exists
  8032. if (rule.attributes[attr]) {
  8033. return true;
  8034. }
  8035. // Check if attribute matches a regexp pattern
  8036. attrPatterns = rule.attributePatterns;
  8037. if (attrPatterns) {
  8038. i = attrPatterns.length;
  8039. while (i--) {
  8040. if (attrPatterns[i].pattern.test(name)) {
  8041. return true;
  8042. }
  8043. }
  8044. }
  8045. } else {
  8046. return true;
  8047. }
  8048. }
  8049. // No match
  8050. return false;
  8051. };
  8052. /**
  8053. * Returns true/false if the specified element is valid or not
  8054. * according to the schema.
  8055. *
  8056. * @method getElementRule
  8057. * @param {String} name Element name to check for.
  8058. * @return {Object} Element object or undefined if the element isn't valid.
  8059. */
  8060. self.getElementRule = getElementRule;
  8061. /**
  8062. * Returns an map object of all custom elements.
  8063. *
  8064. * @method getCustomElements
  8065. * @return {Object} Name/value map object of all custom elements.
  8066. */
  8067. self.getCustomElements = function() {
  8068. return customElementsMap;
  8069. };
  8070. /**
  8071. * Parses a valid elements string and adds it to the schema. The valid elements
  8072. * format is for example "element[attr=default|otherattr]".
  8073. * Existing rules will be replaced with the ones specified, so this extends the schema.
  8074. *
  8075. * @method addValidElements
  8076. * @param {String} valid_elements String in the valid elements format to be parsed.
  8077. */
  8078. self.addValidElements = addValidElements;
  8079. /**
  8080. * Parses a valid elements string and sets it to the schema. The valid elements
  8081. * format is for example "element[attr=default|otherattr]".
  8082. * Existing rules will be replaced with the ones specified, so this extends the schema.
  8083. *
  8084. * @method setValidElements
  8085. * @param {String} valid_elements String in the valid elements format to be parsed.
  8086. */
  8087. self.setValidElements = setValidElements;
  8088. /**
  8089. * Adds custom non HTML elements to the schema.
  8090. *
  8091. * @method addCustomElements
  8092. * @param {String} custom_elements Comma separated list of custom elements to add.
  8093. */
  8094. self.addCustomElements = addCustomElements;
  8095. /**
  8096. * Parses a valid children string and adds them to the schema structure. The valid children
  8097. * format is for example: "element[child1|child2]".
  8098. *
  8099. * @method addValidChildren
  8100. * @param {String} valid_children Valid children elements string to parse
  8101. */
  8102. self.addValidChildren = addValidChildren;
  8103. self.elements = elements;
  8104. };
  8105. });
  8106. // Included from: js/tinymce/classes/html/SaxParser.js
  8107. /**
  8108. * SaxParser.js
  8109. *
  8110. * Copyright, Moxiecode Systems AB
  8111. * Released under LGPL License.
  8112. *
  8113. * License: http://www.tinymce.com/license
  8114. * Contributing: http://www.tinymce.com/contributing
  8115. */
  8116. /*eslint max-depth:[2, 9] */
  8117. /**
  8118. * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
  8119. * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
  8120. * and attributes that doesn't fit the schema if the validate setting is enabled.
  8121. *
  8122. * @example
  8123. * var parser = new tinymce.html.SaxParser({
  8124. * validate: true,
  8125. *
  8126. * comment: function(text) {
  8127. * console.log('Comment:', text);
  8128. * },
  8129. *
  8130. * cdata: function(text) {
  8131. * console.log('CDATA:', text);
  8132. * },
  8133. *
  8134. * text: function(text, raw) {
  8135. * console.log('Text:', text, 'Raw:', raw);
  8136. * },
  8137. *
  8138. * start: function(name, attrs, empty) {
  8139. * console.log('Start:', name, attrs, empty);
  8140. * },
  8141. *
  8142. * end: function(name) {
  8143. * console.log('End:', name);
  8144. * },
  8145. *
  8146. * pi: function(name, text) {
  8147. * console.log('PI:', name, text);
  8148. * },
  8149. *
  8150. * doctype: function(text) {
  8151. * console.log('DocType:', text);
  8152. * }
  8153. * }, schema);
  8154. * @class tinymce.html.SaxParser
  8155. * @version 3.4
  8156. */
  8157. define("tinymce/html/SaxParser", [
  8158. "tinymce/html/Schema",
  8159. "tinymce/html/Entities",
  8160. "tinymce/util/Tools"
  8161. ], function(Schema, Entities, Tools) {
  8162. var each = Tools.each;
  8163. /**
  8164. * Constructs a new SaxParser instance.
  8165. *
  8166. * @constructor
  8167. * @method SaxParser
  8168. * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  8169. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
  8170. */
  8171. return function(settings, schema) {
  8172. var self = this;
  8173. function noop() {}
  8174. settings = settings || {};
  8175. self.schema = schema = schema || new Schema();
  8176. if (settings.fix_self_closing !== false) {
  8177. settings.fix_self_closing = true;
  8178. }
  8179. // Add handler functions from settings and setup default handlers
  8180. each('comment cdata text start end pi doctype'.split(' '), function(name) {
  8181. if (name) {
  8182. self[name] = settings[name] || noop;
  8183. }
  8184. });
  8185. /**
  8186. * Parses the specified HTML string and executes the callbacks for each item it finds.
  8187. *
  8188. * @example
  8189. * new SaxParser({...}).parse('<b>text</b>');
  8190. * @method parse
  8191. * @param {String} html Html string to sax parse.
  8192. */
  8193. self.parse = function(html) {
  8194. var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
  8195. var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
  8196. var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
  8197. var attributesRequired, attributesDefault, attributesForced;
  8198. var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
  8199. var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href');
  8200. var scriptUriRegExp = /(java|vb)script:/i;
  8201. function processEndTag(name) {
  8202. var pos, i;
  8203. // Find position of parent of the same type
  8204. pos = stack.length;
  8205. while (pos--) {
  8206. if (stack[pos].name === name) {
  8207. break;
  8208. }
  8209. }
  8210. // Found parent
  8211. if (pos >= 0) {
  8212. // Close all the open elements
  8213. for (i = stack.length - 1; i >= pos; i--) {
  8214. name = stack[i];
  8215. if (name.valid) {
  8216. self.end(name.name);
  8217. }
  8218. }
  8219. // Remove the open elements from the stack
  8220. stack.length = pos;
  8221. }
  8222. }
  8223. function parseAttribute(match, name, value, val2, val3) {
  8224. var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
  8225. name = name.toLowerCase();
  8226. value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
  8227. // Validate name and value pass through all data- attributes
  8228. if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
  8229. attrRule = validAttributesMap[name];
  8230. // Find rule by pattern matching
  8231. if (!attrRule && validAttributePatterns) {
  8232. i = validAttributePatterns.length;
  8233. while (i--) {
  8234. attrRule = validAttributePatterns[i];
  8235. if (attrRule.pattern.test(name)) {
  8236. break;
  8237. }
  8238. }
  8239. // No rule matched
  8240. if (i === -1) {
  8241. attrRule = null;
  8242. }
  8243. }
  8244. // No attribute rule found
  8245. if (!attrRule) {
  8246. return;
  8247. }
  8248. // Validate value
  8249. if (attrRule.validValues && !(value in attrRule.validValues)) {
  8250. return;
  8251. }
  8252. }
  8253. // Block any javascript: urls
  8254. if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
  8255. var uri = value.replace(trimRegExp, '');
  8256. try {
  8257. // Might throw malformed URI sequence
  8258. uri = decodeURIComponent(uri);
  8259. if (scriptUriRegExp.test(uri)) {
  8260. return;
  8261. }
  8262. } catch (ex) {
  8263. // Fallback to non UTF-8 decoder
  8264. uri = unescape(uri);
  8265. if (scriptUriRegExp.test(uri)) {
  8266. return;
  8267. }
  8268. }
  8269. }
  8270. // Add attribute to list and map
  8271. attrList.map[name] = value;
  8272. attrList.push({
  8273. name: name,
  8274. value: value
  8275. });
  8276. }
  8277. // Precompile RegExps and map objects
  8278. tokenRegExp = new RegExp('<(?:' +
  8279. '(?:!--([\\w\\W]*?)-->)|' + // Comment
  8280. '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
  8281. '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
  8282. '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
  8283. '(?:\\/([^>]+)>)|' + // End element
  8284. '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
  8285. ')', 'g');
  8286. attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
  8287. // Setup lookup tables for empty elements and boolean attributes
  8288. shortEndedElements = schema.getShortEndedElements();
  8289. selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
  8290. fillAttrsMap = schema.getBoolAttrs();
  8291. validate = settings.validate;
  8292. removeInternalElements = settings.remove_internals;
  8293. fixSelfClosing = settings.fix_self_closing;
  8294. specialElements = schema.getSpecialElements();
  8295. while ((matches = tokenRegExp.exec(html))) {
  8296. // Text
  8297. if (index < matches.index) {
  8298. self.text(decode(html.substr(index, matches.index - index)));
  8299. }
  8300. if ((value = matches[6])) { // End element
  8301. value = value.toLowerCase();
  8302. // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  8303. if (value.charAt(0) === ':') {
  8304. value = value.substr(1);
  8305. }
  8306. processEndTag(value);
  8307. } else if ((value = matches[7])) { // Start element
  8308. value = value.toLowerCase();
  8309. // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
  8310. if (value.charAt(0) === ':') {
  8311. value = value.substr(1);
  8312. }
  8313. isShortEnded = value in shortEndedElements;
  8314. // Is self closing tag for example an <li> after an open <li>
  8315. if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
  8316. processEndTag(value);
  8317. }
  8318. // Validate element
  8319. if (!validate || (elementRule = schema.getElementRule(value))) {
  8320. isValidElement = true;
  8321. // Grab attributes map and patters when validation is enabled
  8322. if (validate) {
  8323. validAttributesMap = elementRule.attributes;
  8324. validAttributePatterns = elementRule.attributePatterns;
  8325. }
  8326. // Parse attributes
  8327. if ((attribsValue = matches[8])) {
  8328. isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
  8329. // If the element has internal attributes then remove it if we are told to do so
  8330. if (isInternalElement && removeInternalElements) {
  8331. isValidElement = false;
  8332. }
  8333. attrList = [];
  8334. attrList.map = {};
  8335. attribsValue.replace(attrRegExp, parseAttribute);
  8336. } else {
  8337. attrList = [];
  8338. attrList.map = {};
  8339. }
  8340. // Process attributes if validation is enabled
  8341. if (validate && !isInternalElement) {
  8342. attributesRequired = elementRule.attributesRequired;
  8343. attributesDefault = elementRule.attributesDefault;
  8344. attributesForced = elementRule.attributesForced;
  8345. anyAttributesRequired = elementRule.removeEmptyAttrs;
  8346. // Check if any attribute exists
  8347. if (anyAttributesRequired && !attrList.length) {
  8348. isValidElement = false;
  8349. }
  8350. // Handle forced attributes
  8351. if (attributesForced) {
  8352. i = attributesForced.length;
  8353. while (i--) {
  8354. attr = attributesForced[i];
  8355. name = attr.name;
  8356. attrValue = attr.value;
  8357. if (attrValue === '{$uid}') {
  8358. attrValue = 'mce_' + idCount++;
  8359. }
  8360. attrList.map[name] = attrValue;
  8361. attrList.push({name: name, value: attrValue});
  8362. }
  8363. }
  8364. // Handle default attributes
  8365. if (attributesDefault) {
  8366. i = attributesDefault.length;
  8367. while (i--) {
  8368. attr = attributesDefault[i];
  8369. name = attr.name;
  8370. if (!(name in attrList.map)) {
  8371. attrValue = attr.value;
  8372. if (attrValue === '{$uid}') {
  8373. attrValue = 'mce_' + idCount++;
  8374. }
  8375. attrList.map[name] = attrValue;
  8376. attrList.push({name: name, value: attrValue});
  8377. }
  8378. }
  8379. }
  8380. // Handle required attributes
  8381. if (attributesRequired) {
  8382. i = attributesRequired.length;
  8383. while (i--) {
  8384. if (attributesRequired[i] in attrList.map) {
  8385. break;
  8386. }
  8387. }
  8388. // None of the required attributes where found
  8389. if (i === -1) {
  8390. isValidElement = false;
  8391. }
  8392. }
  8393. // Invalidate element if it's marked as bogus
  8394. if (attrList.map['data-mce-bogus']) {
  8395. isValidElement = false;
  8396. }
  8397. }
  8398. if (isValidElement) {
  8399. self.start(value, attrList, isShortEnded);
  8400. }
  8401. } else {
  8402. isValidElement = false;
  8403. }
  8404. // Treat script, noscript and style a bit different since they may include code that looks like elements
  8405. if ((endRegExp = specialElements[value])) {
  8406. endRegExp.lastIndex = index = matches.index + matches[0].length;
  8407. if ((matches = endRegExp.exec(html))) {
  8408. if (isValidElement) {
  8409. text = html.substr(index, matches.index - index);
  8410. }
  8411. index = matches.index + matches[0].length;
  8412. } else {
  8413. text = html.substr(index);
  8414. index = html.length;
  8415. }
  8416. if (isValidElement) {
  8417. if (text.length > 0) {
  8418. self.text(text, true);
  8419. }
  8420. self.end(value);
  8421. }
  8422. tokenRegExp.lastIndex = index;
  8423. continue;
  8424. }
  8425. // Push value on to stack
  8426. if (!isShortEnded) {
  8427. if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
  8428. stack.push({name: value, valid: isValidElement});
  8429. } else if (isValidElement) {
  8430. self.end(value);
  8431. }
  8432. }
  8433. } else if ((value = matches[1])) { // Comment
  8434. // Padd comment value to avoid browsers from parsing invalid comments as HTML
  8435. if (value.charAt(0) === '>') {
  8436. value = ' ' + value;
  8437. }
  8438. if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
  8439. value = ' ' + value;
  8440. }
  8441. self.comment(value);
  8442. } else if ((value = matches[2])) { // CDATA
  8443. self.cdata(value);
  8444. } else if ((value = matches[3])) { // DOCTYPE
  8445. self.doctype(value);
  8446. } else if ((value = matches[4])) { // PI
  8447. self.pi(value, matches[5]);
  8448. }
  8449. index = matches.index + matches[0].length;
  8450. }
  8451. // Text
  8452. if (index < html.length) {
  8453. self.text(decode(html.substr(index)));
  8454. }
  8455. // Close any open elements
  8456. for (i = stack.length - 1; i >= 0; i--) {
  8457. value = stack[i];
  8458. if (value.valid) {
  8459. self.end(value.name);
  8460. }
  8461. }
  8462. };
  8463. };
  8464. });
  8465. // Included from: js/tinymce/classes/html/DomParser.js
  8466. /**
  8467. * DomParser.js
  8468. *
  8469. * Copyright, Moxiecode Systems AB
  8470. * Released under LGPL License.
  8471. *
  8472. * License: http://www.tinymce.com/license
  8473. * Contributing: http://www.tinymce.com/contributing
  8474. */
  8475. /**
  8476. * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
  8477. * sure that the node tree is valid according to the specified schema.
  8478. * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
  8479. *
  8480. * @example
  8481. * var parser = new tinymce.html.DomParser({validate: true}, schema);
  8482. * var rootNode = parser.parse('<h1>content</h1>');
  8483. *
  8484. * @class tinymce.html.DomParser
  8485. * @version 3.4
  8486. */
  8487. define("tinymce/html/DomParser", [
  8488. "tinymce/html/Node",
  8489. "tinymce/html/Schema",
  8490. "tinymce/html/SaxParser",
  8491. "tinymce/util/Tools"
  8492. ], function(Node, Schema, SaxParser, Tools) {
  8493. var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
  8494. /**
  8495. * Constructs a new DomParser instance.
  8496. *
  8497. * @constructor
  8498. * @method DomParser
  8499. * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
  8500. * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
  8501. */
  8502. return function(settings, schema) {
  8503. var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
  8504. settings = settings || {};
  8505. settings.validate = "validate" in settings ? settings.validate : true;
  8506. settings.root_name = settings.root_name || 'body';
  8507. self.schema = schema = schema || new Schema();
  8508. function fixInvalidChildren(nodes) {
  8509. var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
  8510. var nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
  8511. nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
  8512. nonEmptyElements = schema.getNonEmptyElements();
  8513. textBlockElements = schema.getTextBlockElements();
  8514. for (ni = 0; ni < nodes.length; ni++) {
  8515. node = nodes[ni];
  8516. // Already removed or fixed
  8517. if (!node.parent || node.fixed) {
  8518. continue;
  8519. }
  8520. // If the invalid element is a text block and the text block is within a parent LI element
  8521. // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
  8522. if (textBlockElements[node.name] && node.parent.name == 'li') {
  8523. // Move sibling text blocks after LI element
  8524. sibling = node.next;
  8525. while (sibling) {
  8526. if (textBlockElements[sibling.name]) {
  8527. sibling.name = 'li';
  8528. sibling.fixed = true;
  8529. node.parent.insert(sibling, node.parent);
  8530. } else {
  8531. break;
  8532. }
  8533. sibling = sibling.next;
  8534. }
  8535. // Unwrap current text block
  8536. node.unwrap(node);
  8537. continue;
  8538. }
  8539. // Get list of all parent nodes until we find a valid parent to stick the child into
  8540. parents = [node];
  8541. for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
  8542. !nonSplitableElements[parent.name]; parent = parent.parent) {
  8543. parents.push(parent);
  8544. }
  8545. // Found a suitable parent
  8546. if (parent && parents.length > 1) {
  8547. // Reverse the array since it makes looping easier
  8548. parents.reverse();
  8549. // Clone the related parent and insert that after the moved node
  8550. newParent = currentNode = self.filterNode(parents[0].clone());
  8551. // Start cloning and moving children on the left side of the target node
  8552. for (i = 0; i < parents.length - 1; i++) {
  8553. if (schema.isValidChild(currentNode.name, parents[i].name)) {
  8554. tempNode = self.filterNode(parents[i].clone());
  8555. currentNode.append(tempNode);
  8556. } else {
  8557. tempNode = currentNode;
  8558. }
  8559. for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
  8560. nextNode = childNode.next;
  8561. tempNode.append(childNode);
  8562. childNode = nextNode;
  8563. }
  8564. currentNode = tempNode;
  8565. }
  8566. if (!newParent.isEmpty(nonEmptyElements)) {
  8567. parent.insert(newParent, parents[0], true);
  8568. parent.insert(node, newParent);
  8569. } else {
  8570. parent.insert(node, parents[0], true);
  8571. }
  8572. // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
  8573. parent = parents[0];
  8574. if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
  8575. parent.empty().remove();
  8576. }
  8577. } else if (node.parent) {
  8578. // If it's an LI try to find a UL/OL for it or wrap it
  8579. if (node.name === 'li') {
  8580. sibling = node.prev;
  8581. if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  8582. sibling.append(node);
  8583. continue;
  8584. }
  8585. sibling = node.next;
  8586. if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
  8587. sibling.insert(node, sibling.firstChild, true);
  8588. continue;
  8589. }
  8590. node.wrap(self.filterNode(new Node('ul', 1)));
  8591. continue;
  8592. }
  8593. // Try wrapping the element in a DIV
  8594. if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
  8595. node.wrap(self.filterNode(new Node('div', 1)));
  8596. } else {
  8597. // We failed wrapping it, then remove or unwrap it
  8598. if (node.name === 'style' || node.name === 'script') {
  8599. node.empty().remove();
  8600. } else {
  8601. node.unwrap();
  8602. }
  8603. }
  8604. }
  8605. }
  8606. }
  8607. /**
  8608. * Runs the specified node though the element and attributes filters.
  8609. *
  8610. * @method filterNode
  8611. * @param {tinymce.html.Node} Node the node to run filters on.
  8612. * @return {tinymce.html.Node} The passed in node.
  8613. */
  8614. self.filterNode = function(node) {
  8615. var i, name, list;
  8616. // Run element filters
  8617. if (name in nodeFilters) {
  8618. list = matchedNodes[name];
  8619. if (list) {
  8620. list.push(node);
  8621. } else {
  8622. matchedNodes[name] = [node];
  8623. }
  8624. }
  8625. // Run attribute filters
  8626. i = attributeFilters.length;
  8627. while (i--) {
  8628. name = attributeFilters[i].name;
  8629. if (name in node.attributes.map) {
  8630. list = matchedAttributes[name];
  8631. if (list) {
  8632. list.push(node);
  8633. } else {
  8634. matchedAttributes[name] = [node];
  8635. }
  8636. }
  8637. }
  8638. return node;
  8639. };
  8640. /**
  8641. * Adds a node filter function to the parser, the parser will collect the specified nodes by name
  8642. * and then execute the callback ones it has finished parsing the document.
  8643. *
  8644. * @example
  8645. * parser.addNodeFilter('p,h1', function(nodes, name) {
  8646. * for (var i = 0; i < nodes.length; i++) {
  8647. * console.log(nodes[i].name);
  8648. * }
  8649. * });
  8650. * @method addNodeFilter
  8651. * @method {String} name Comma separated list of nodes to collect.
  8652. * @param {function} callback Callback function to execute once it has collected nodes.
  8653. */
  8654. self.addNodeFilter = function(name, callback) {
  8655. each(explode(name), function(name) {
  8656. var list = nodeFilters[name];
  8657. if (!list) {
  8658. nodeFilters[name] = list = [];
  8659. }
  8660. list.push(callback);
  8661. });
  8662. };
  8663. /**
  8664. * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
  8665. * and then execute the callback ones it has finished parsing the document.
  8666. *
  8667. * @example
  8668. * parser.addAttributeFilter('src,href', function(nodes, name) {
  8669. * for (var i = 0; i < nodes.length; i++) {
  8670. * console.log(nodes[i].name);
  8671. * }
  8672. * });
  8673. * @method addAttributeFilter
  8674. * @method {String} name Comma separated list of nodes to collect.
  8675. * @param {function} callback Callback function to execute once it has collected nodes.
  8676. */
  8677. self.addAttributeFilter = function(name, callback) {
  8678. each(explode(name), function(name) {
  8679. var i;
  8680. for (i = 0; i < attributeFilters.length; i++) {
  8681. if (attributeFilters[i].name === name) {
  8682. attributeFilters[i].callbacks.push(callback);
  8683. return;
  8684. }
  8685. }
  8686. attributeFilters.push({name: name, callbacks: [callback]});
  8687. });
  8688. };
  8689. /**
  8690. * Parses the specified HTML string into a DOM like node tree and returns the result.
  8691. *
  8692. * @example
  8693. * var rootNode = new DomParser({...}).parse('<b>text</b>');
  8694. * @method parse
  8695. * @param {String} html Html string to sax parse.
  8696. * @param {Object} args Optional args object that gets passed to all filter functions.
  8697. * @return {tinymce.html.Node} Root node containing the tree.
  8698. */
  8699. self.parse = function(html, args) {
  8700. var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
  8701. var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
  8702. var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
  8703. var children, nonEmptyElements, rootBlockName;
  8704. args = args || {};
  8705. matchedNodes = {};
  8706. matchedAttributes = {};
  8707. blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
  8708. nonEmptyElements = schema.getNonEmptyElements();
  8709. children = schema.children;
  8710. validate = settings.validate;
  8711. rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
  8712. whiteSpaceElements = schema.getWhiteSpaceElements();
  8713. startWhiteSpaceRegExp = /^[ \t\r\n]+/;
  8714. endWhiteSpaceRegExp = /[ \t\r\n]+$/;
  8715. allWhiteSpaceRegExp = /[ \t\r\n]+/g;
  8716. isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
  8717. function addRootBlocks() {
  8718. var node = rootNode.firstChild, next, rootBlockNode;
  8719. // Removes whitespace at beginning and end of block so:
  8720. // <p> x </p> -> <p>x</p>
  8721. function trim(rootBlockNode) {
  8722. if (rootBlockNode) {
  8723. node = rootBlockNode.firstChild;
  8724. if (node && node.type == 3) {
  8725. node.value = node.value.replace(startWhiteSpaceRegExp, '');
  8726. }
  8727. node = rootBlockNode.lastChild;
  8728. if (node && node.type == 3) {
  8729. node.value = node.value.replace(endWhiteSpaceRegExp, '');
  8730. }
  8731. }
  8732. }
  8733. // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
  8734. if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
  8735. return;
  8736. }
  8737. while (node) {
  8738. next = node.next;
  8739. if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
  8740. !blockElements[node.name] && !node.attr('data-mce-type'))) {
  8741. if (!rootBlockNode) {
  8742. // Create a new root block element
  8743. rootBlockNode = createNode(rootBlockName, 1);
  8744. rootBlockNode.attr(settings.forced_root_block_attrs);
  8745. rootNode.insert(rootBlockNode, node);
  8746. rootBlockNode.append(node);
  8747. } else {
  8748. rootBlockNode.append(node);
  8749. }
  8750. } else {
  8751. trim(rootBlockNode);
  8752. rootBlockNode = null;
  8753. }
  8754. node = next;
  8755. }
  8756. trim(rootBlockNode);
  8757. }
  8758. function createNode(name, type) {
  8759. var node = new Node(name, type), list;
  8760. if (name in nodeFilters) {
  8761. list = matchedNodes[name];
  8762. if (list) {
  8763. list.push(node);
  8764. } else {
  8765. matchedNodes[name] = [node];
  8766. }
  8767. }
  8768. return node;
  8769. }
  8770. function removeWhitespaceBefore(node) {
  8771. var textNode, textVal, sibling;
  8772. for (textNode = node.prev; textNode && textNode.type === 3; ) {
  8773. textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
  8774. if (textVal.length > 0) {
  8775. textNode.value = textVal;
  8776. textNode = textNode.prev;
  8777. } else {
  8778. sibling = textNode.prev;
  8779. textNode.remove();
  8780. textNode = sibling;
  8781. }
  8782. }
  8783. }
  8784. function cloneAndExcludeBlocks(input) {
  8785. var name, output = {};
  8786. for (name in input) {
  8787. if (name !== 'li' && name != 'p') {
  8788. output[name] = input[name];
  8789. }
  8790. }
  8791. return output;
  8792. }
  8793. parser = new SaxParser({
  8794. validate: validate,
  8795. allow_script_urls: settings.allow_script_urls,
  8796. allow_conditional_comments: settings.allow_conditional_comments,
  8797. // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
  8798. self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
  8799. cdata: function(text) {
  8800. node.append(createNode('#cdata', 4)).value = text;
  8801. },
  8802. text: function(text, raw) {
  8803. var textNode;
  8804. // Trim all redundant whitespace on non white space elements
  8805. if (!isInWhiteSpacePreservedElement) {
  8806. text = text.replace(allWhiteSpaceRegExp, ' ');
  8807. if (node.lastChild && blockElements[node.lastChild.name]) {
  8808. text = text.replace(startWhiteSpaceRegExp, '');
  8809. }
  8810. }
  8811. // Do we need to create the node
  8812. if (text.length !== 0) {
  8813. textNode = createNode('#text', 3);
  8814. textNode.raw = !!raw;
  8815. node.append(textNode).value = text;
  8816. }
  8817. },
  8818. comment: function(text) {
  8819. node.append(createNode('#comment', 8)).value = text;
  8820. },
  8821. pi: function(name, text) {
  8822. node.append(createNode(name, 7)).value = text;
  8823. removeWhitespaceBefore(node);
  8824. },
  8825. doctype: function(text) {
  8826. var newNode;
  8827. newNode = node.append(createNode('#doctype', 10));
  8828. newNode.value = text;
  8829. removeWhitespaceBefore(node);
  8830. },
  8831. start: function(name, attrs, empty) {
  8832. var newNode, attrFiltersLen, elementRule, attrName, parent;
  8833. elementRule = validate ? schema.getElementRule(name) : {};
  8834. if (elementRule) {
  8835. newNode = createNode(elementRule.outputName || name, 1);
  8836. newNode.attributes = attrs;
  8837. newNode.shortEnded = empty;
  8838. node.append(newNode);
  8839. // Check if node is valid child of the parent node is the child is
  8840. // unknown we don't collect it since it's probably a custom element
  8841. parent = children[node.name];
  8842. if (parent && children[newNode.name] && !parent[newNode.name]) {
  8843. invalidChildren.push(newNode);
  8844. }
  8845. attrFiltersLen = attributeFilters.length;
  8846. while (attrFiltersLen--) {
  8847. attrName = attributeFilters[attrFiltersLen].name;
  8848. if (attrName in attrs.map) {
  8849. list = matchedAttributes[attrName];
  8850. if (list) {
  8851. list.push(newNode);
  8852. } else {
  8853. matchedAttributes[attrName] = [newNode];
  8854. }
  8855. }
  8856. }
  8857. // Trim whitespace before block
  8858. if (blockElements[name]) {
  8859. removeWhitespaceBefore(newNode);
  8860. }
  8861. // Change current node if the element wasn't empty i.e not <br /> or <img />
  8862. if (!empty) {
  8863. node = newNode;
  8864. }
  8865. // Check if we are inside a whitespace preserved element
  8866. if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  8867. isInWhiteSpacePreservedElement = true;
  8868. }
  8869. }
  8870. },
  8871. end: function(name) {
  8872. var textNode, elementRule, text, sibling, tempNode;
  8873. elementRule = validate ? schema.getElementRule(name) : {};
  8874. if (elementRule) {
  8875. if (blockElements[name]) {
  8876. if (!isInWhiteSpacePreservedElement) {
  8877. // Trim whitespace of the first node in a block
  8878. textNode = node.firstChild;
  8879. if (textNode && textNode.type === 3) {
  8880. text = textNode.value.replace(startWhiteSpaceRegExp, '');
  8881. // Any characters left after trim or should we remove it
  8882. if (text.length > 0) {
  8883. textNode.value = text;
  8884. textNode = textNode.next;
  8885. } else {
  8886. sibling = textNode.next;
  8887. textNode.remove();
  8888. textNode = sibling;
  8889. // Remove any pure whitespace siblings
  8890. while (textNode && textNode.type === 3) {
  8891. text = textNode.value;
  8892. sibling = textNode.next;
  8893. if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  8894. textNode.remove();
  8895. textNode = sibling;
  8896. }
  8897. textNode = sibling;
  8898. }
  8899. }
  8900. }
  8901. // Trim whitespace of the last node in a block
  8902. textNode = node.lastChild;
  8903. if (textNode && textNode.type === 3) {
  8904. text = textNode.value.replace(endWhiteSpaceRegExp, '');
  8905. // Any characters left after trim or should we remove it
  8906. if (text.length > 0) {
  8907. textNode.value = text;
  8908. textNode = textNode.prev;
  8909. } else {
  8910. sibling = textNode.prev;
  8911. textNode.remove();
  8912. textNode = sibling;
  8913. // Remove any pure whitespace siblings
  8914. while (textNode && textNode.type === 3) {
  8915. text = textNode.value;
  8916. sibling = textNode.prev;
  8917. if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
  8918. textNode.remove();
  8919. textNode = sibling;
  8920. }
  8921. textNode = sibling;
  8922. }
  8923. }
  8924. }
  8925. }
  8926. // Trim start white space
  8927. // Removed due to: #5424
  8928. /*textNode = node.prev;
  8929. if (textNode && textNode.type === 3) {
  8930. text = textNode.value.replace(startWhiteSpaceRegExp, '');
  8931. if (text.length > 0)
  8932. textNode.value = text;
  8933. else
  8934. textNode.remove();
  8935. }*/
  8936. }
  8937. // Check if we exited a whitespace preserved element
  8938. if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
  8939. isInWhiteSpacePreservedElement = false;
  8940. }
  8941. // Handle empty nodes
  8942. if (elementRule.removeEmpty || elementRule.paddEmpty) {
  8943. if (node.isEmpty(nonEmptyElements)) {
  8944. if (elementRule.paddEmpty) {
  8945. node.empty().append(new Node('#text', '3')).value = '\u00a0';
  8946. } else {
  8947. // Leave nodes that have a name like <a name="name">
  8948. if (!node.attributes.map.name && !node.attributes.map.id) {
  8949. tempNode = node.parent;
  8950. node.empty().remove();
  8951. node = tempNode;
  8952. return;
  8953. }
  8954. }
  8955. }
  8956. }
  8957. node = node.parent;
  8958. }
  8959. }
  8960. }, schema);
  8961. rootNode = node = new Node(args.context || settings.root_name, 11);
  8962. parser.parse(html);
  8963. // Fix invalid children or report invalid children in a contextual parsing
  8964. if (validate && invalidChildren.length) {
  8965. if (!args.context) {
  8966. fixInvalidChildren(invalidChildren);
  8967. } else {
  8968. args.invalid = true;
  8969. }
  8970. }
  8971. // Wrap nodes in the root into block elements if the root is body
  8972. if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
  8973. addRootBlocks();
  8974. }
  8975. // Run filters only when the contents is valid
  8976. if (!args.invalid) {
  8977. // Run node filters
  8978. for (name in matchedNodes) {
  8979. list = nodeFilters[name];
  8980. nodes = matchedNodes[name];
  8981. // Remove already removed children
  8982. fi = nodes.length;
  8983. while (fi--) {
  8984. if (!nodes[fi].parent) {
  8985. nodes.splice(fi, 1);
  8986. }
  8987. }
  8988. for (i = 0, l = list.length; i < l; i++) {
  8989. list[i](nodes, name, args);
  8990. }
  8991. }
  8992. // Run attribute filters
  8993. for (i = 0, l = attributeFilters.length; i < l; i++) {
  8994. list = attributeFilters[i];
  8995. if (list.name in matchedAttributes) {
  8996. nodes = matchedAttributes[list.name];
  8997. // Remove already removed children
  8998. fi = nodes.length;
  8999. while (fi--) {
  9000. if (!nodes[fi].parent) {
  9001. nodes.splice(fi, 1);
  9002. }
  9003. }
  9004. for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
  9005. list.callbacks[fi](nodes, list.name, args);
  9006. }
  9007. }
  9008. }
  9009. }
  9010. return rootNode;
  9011. };
  9012. // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
  9013. // make it possible to place the caret inside empty blocks. This logic tries to remove
  9014. // these elements and keep br elements that where intended to be there intact
  9015. if (settings.remove_trailing_brs) {
  9016. self.addNodeFilter('br', function(nodes) {
  9017. var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
  9018. var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
  9019. var elementRule, textNode;
  9020. // Remove brs from body element as well
  9021. blockElements.body = 1;
  9022. // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
  9023. for (i = 0; i < l; i++) {
  9024. node = nodes[i];
  9025. parent = node.parent;
  9026. if (blockElements[node.parent.name] && node === parent.lastChild) {
  9027. // Loop all nodes to the left of the current node and check for other BR elements
  9028. // excluding bookmarks since they are invisible
  9029. prev = node.prev;
  9030. while (prev) {
  9031. prevName = prev.name;
  9032. // Ignore bookmarks
  9033. if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
  9034. // Found a non BR element
  9035. if (prevName !== "br") {
  9036. break;
  9037. }
  9038. // Found another br it's a <br><br> structure then don't remove anything
  9039. if (prevName === 'br') {
  9040. node = null;
  9041. break;
  9042. }
  9043. }
  9044. prev = prev.prev;
  9045. }
  9046. if (node) {
  9047. node.remove();
  9048. // Is the parent to be considered empty after we removed the BR
  9049. if (parent.isEmpty(nonEmptyElements)) {
  9050. elementRule = schema.getElementRule(parent.name);
  9051. // Remove or padd the element depending on schema rule
  9052. if (elementRule) {
  9053. if (elementRule.removeEmpty) {
  9054. parent.remove();
  9055. } else if (elementRule.paddEmpty) {
  9056. parent.empty().append(new Node('#text', 3)).value = '\u00a0';
  9057. }
  9058. }
  9059. }
  9060. }
  9061. } else {
  9062. // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
  9063. // so they become <p><b><i>&nbsp;</i></b></p>
  9064. lastParent = node;
  9065. while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
  9066. lastParent = parent;
  9067. if (blockElements[parent.name]) {
  9068. break;
  9069. }
  9070. parent = parent.parent;
  9071. }
  9072. if (lastParent === parent) {
  9073. textNode = new Node('#text', 3);
  9074. textNode.value = '\u00a0';
  9075. node.replace(textNode);
  9076. }
  9077. }
  9078. }
  9079. });
  9080. }
  9081. // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
  9082. if (!settings.allow_html_in_named_anchor) {
  9083. self.addAttributeFilter('id,name', function(nodes) {
  9084. var i = nodes.length, sibling, prevSibling, parent, node;
  9085. while (i--) {
  9086. node = nodes[i];
  9087. if (node.name === 'a' && node.firstChild && !node.attr('href')) {
  9088. parent = node.parent;
  9089. // Move children after current node
  9090. sibling = node.lastChild;
  9091. do {
  9092. prevSibling = sibling.prev;
  9093. parent.insert(sibling, node);
  9094. sibling = prevSibling;
  9095. } while (sibling);
  9096. }
  9097. }
  9098. });
  9099. }
  9100. };
  9101. });
  9102. // Included from: js/tinymce/classes/html/Writer.js
  9103. /**
  9104. * Writer.js
  9105. *
  9106. * Copyright, Moxiecode Systems AB
  9107. * Released under LGPL License.
  9108. *
  9109. * License: http://www.tinymce.com/license
  9110. * Contributing: http://www.tinymce.com/contributing
  9111. */
  9112. /**
  9113. * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
  9114. *
  9115. * @class tinymce.html.Writer
  9116. * @example
  9117. * var writer = new tinymce.html.Writer({indent: true});
  9118. * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
  9119. * console.log(writer.getContent());
  9120. *
  9121. * @class tinymce.html.Writer
  9122. * @version 3.4
  9123. */
  9124. define("tinymce/html/Writer", [
  9125. "tinymce/html/Entities",
  9126. "tinymce/util/Tools"
  9127. ], function(Entities, Tools) {
  9128. var makeMap = Tools.makeMap;
  9129. /**
  9130. * Constructs a new Writer instance.
  9131. *
  9132. * @constructor
  9133. * @method Writer
  9134. * @param {Object} settings Name/value settings object.
  9135. */
  9136. return function(settings) {
  9137. var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
  9138. settings = settings || {};
  9139. indent = settings.indent;
  9140. indentBefore = makeMap(settings.indent_before || '');
  9141. indentAfter = makeMap(settings.indent_after || '');
  9142. encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
  9143. htmlOutput = settings.element_format == "html";
  9144. return {
  9145. /**
  9146. * Writes the a start element such as <p id="a">.
  9147. *
  9148. * @method start
  9149. * @param {String} name Name of the element.
  9150. * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
  9151. * @param {Boolean} empty Optional empty state if the tag should end like <br />.
  9152. */
  9153. start: function(name, attrs, empty) {
  9154. var i, l, attr, value;
  9155. if (indent && indentBefore[name] && html.length > 0) {
  9156. value = html[html.length - 1];
  9157. if (value.length > 0 && value !== '\n') {
  9158. html.push('\n');
  9159. }
  9160. }
  9161. html.push('<', name);
  9162. if (attrs) {
  9163. for (i = 0, l = attrs.length; i < l; i++) {
  9164. attr = attrs[i];
  9165. html.push(' ', attr.name, '="', encode(attr.value, true), '"');
  9166. }
  9167. }
  9168. if (!empty || htmlOutput) {
  9169. html[html.length] = '>';
  9170. } else {
  9171. html[html.length] = ' />';
  9172. }
  9173. if (empty && indent && indentAfter[name] && html.length > 0) {
  9174. value = html[html.length - 1];
  9175. if (value.length > 0 && value !== '\n') {
  9176. html.push('\n');
  9177. }
  9178. }
  9179. },
  9180. /**
  9181. * Writes the a end element such as </p>.
  9182. *
  9183. * @method end
  9184. * @param {String} name Name of the element.
  9185. */
  9186. end: function(name) {
  9187. var value;
  9188. /*if (indent && indentBefore[name] && html.length > 0) {
  9189. value = html[html.length - 1];
  9190. if (value.length > 0 && value !== '\n')
  9191. html.push('\n');
  9192. }*/
  9193. html.push('</', name, '>');
  9194. if (indent && indentAfter[name] && html.length > 0) {
  9195. value = html[html.length - 1];
  9196. if (value.length > 0 && value !== '\n') {
  9197. html.push('\n');
  9198. }
  9199. }
  9200. },
  9201. /**
  9202. * Writes a text node.
  9203. *
  9204. * @method text
  9205. * @param {String} text String to write out.
  9206. * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
  9207. */
  9208. text: function(text, raw) {
  9209. if (text.length > 0) {
  9210. html[html.length] = raw ? text : encode(text);
  9211. }
  9212. },
  9213. /**
  9214. * Writes a cdata node such as <![CDATA[data]]>.
  9215. *
  9216. * @method cdata
  9217. * @param {String} text String to write out inside the cdata.
  9218. */
  9219. cdata: function(text) {
  9220. html.push('<![CDATA[', text, ']]>');
  9221. },
  9222. /**
  9223. * Writes a comment node such as <!-- Comment -->.
  9224. *
  9225. * @method cdata
  9226. * @param {String} text String to write out inside the comment.
  9227. */
  9228. comment: function(text) {
  9229. html.push('<!--', text, '-->');
  9230. },
  9231. /**
  9232. * Writes a PI node such as <?xml attr="value" ?>.
  9233. *
  9234. * @method pi
  9235. * @param {String} name Name of the pi.
  9236. * @param {String} text String to write out inside the pi.
  9237. */
  9238. pi: function(name, text) {
  9239. if (text) {
  9240. html.push('<?', name, ' ', text, '?>');
  9241. } else {
  9242. html.push('<?', name, '?>');
  9243. }
  9244. if (indent) {
  9245. html.push('\n');
  9246. }
  9247. },
  9248. /**
  9249. * Writes a doctype node such as <!DOCTYPE data>.
  9250. *
  9251. * @method doctype
  9252. * @param {String} text String to write out inside the doctype.
  9253. */
  9254. doctype: function(text) {
  9255. html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
  9256. },
  9257. /**
  9258. * Resets the internal buffer if one wants to reuse the writer.
  9259. *
  9260. * @method reset
  9261. */
  9262. reset: function() {
  9263. html.length = 0;
  9264. },
  9265. /**
  9266. * Returns the contents that got serialized.
  9267. *
  9268. * @method getContent
  9269. * @return {String} HTML contents that got written down.
  9270. */
  9271. getContent: function() {
  9272. return html.join('').replace(/\n$/, '');
  9273. }
  9274. };
  9275. };
  9276. });
  9277. // Included from: js/tinymce/classes/html/Serializer.js
  9278. /**
  9279. * Serializer.js
  9280. *
  9281. * Copyright, Moxiecode Systems AB
  9282. * Released under LGPL License.
  9283. *
  9284. * License: http://www.tinymce.com/license
  9285. * Contributing: http://www.tinymce.com/contributing
  9286. */
  9287. /**
  9288. * This class is used to serialize down the DOM tree into a string using a Writer instance.
  9289. *
  9290. *
  9291. * @example
  9292. * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
  9293. * @class tinymce.html.Serializer
  9294. * @version 3.4
  9295. */
  9296. define("tinymce/html/Serializer", [
  9297. "tinymce/html/Writer",
  9298. "tinymce/html/Schema"
  9299. ], function(Writer, Schema) {
  9300. /**
  9301. * Constructs a new Serializer instance.
  9302. *
  9303. * @constructor
  9304. * @method Serializer
  9305. * @param {Object} settings Name/value settings object.
  9306. * @param {tinymce.html.Schema} schema Schema instance to use.
  9307. */
  9308. return function(settings, schema) {
  9309. var self = this, writer = new Writer(settings);
  9310. settings = settings || {};
  9311. settings.validate = "validate" in settings ? settings.validate : true;
  9312. self.schema = schema = schema || new Schema();
  9313. self.writer = writer;
  9314. /**
  9315. * Serializes the specified node into a string.
  9316. *
  9317. * @example
  9318. * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
  9319. * @method serialize
  9320. * @param {tinymce.html.Node} node Node instance to serialize.
  9321. * @return {String} String with HTML based on DOM tree.
  9322. */
  9323. self.serialize = function(node) {
  9324. var handlers, validate;
  9325. validate = settings.validate;
  9326. handlers = {
  9327. // #text
  9328. 3: function(node) {
  9329. writer.text(node.value, node.raw);
  9330. },
  9331. // #comment
  9332. 8: function(node) {
  9333. writer.comment(node.value);
  9334. },
  9335. // Processing instruction
  9336. 7: function(node) {
  9337. writer.pi(node.name, node.value);
  9338. },
  9339. // Doctype
  9340. 10: function(node) {
  9341. writer.doctype(node.value);
  9342. },
  9343. // CDATA
  9344. 4: function(node) {
  9345. writer.cdata(node.value);
  9346. },
  9347. // Document fragment
  9348. 11: function(node) {
  9349. if ((node = node.firstChild)) {
  9350. do {
  9351. walk(node);
  9352. } while ((node = node.next));
  9353. }
  9354. }
  9355. };
  9356. writer.reset();
  9357. function walk(node) {
  9358. var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
  9359. if (!handler) {
  9360. name = node.name;
  9361. isEmpty = node.shortEnded;
  9362. attrs = node.attributes;
  9363. // Sort attributes
  9364. if (validate && attrs && attrs.length > 1) {
  9365. sortedAttrs = [];
  9366. sortedAttrs.map = {};
  9367. elementRule = schema.getElementRule(node.name);
  9368. for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
  9369. attrName = elementRule.attributesOrder[i];
  9370. if (attrName in attrs.map) {
  9371. attrValue = attrs.map[attrName];
  9372. sortedAttrs.map[attrName] = attrValue;
  9373. sortedAttrs.push({name: attrName, value: attrValue});
  9374. }
  9375. }
  9376. for (i = 0, l = attrs.length; i < l; i++) {
  9377. attrName = attrs[i].name;
  9378. if (!(attrName in sortedAttrs.map)) {
  9379. attrValue = attrs.map[attrName];
  9380. sortedAttrs.map[attrName] = attrValue;
  9381. sortedAttrs.push({name: attrName, value: attrValue});
  9382. }
  9383. }
  9384. attrs = sortedAttrs;
  9385. }
  9386. writer.start(node.name, attrs, isEmpty);
  9387. if (!isEmpty) {
  9388. if ((node = node.firstChild)) {
  9389. do {
  9390. walk(node);
  9391. } while ((node = node.next));
  9392. }
  9393. writer.end(name);
  9394. }
  9395. } else {
  9396. handler(node);
  9397. }
  9398. }
  9399. // Serialize element and treat all non elements as fragments
  9400. if (node.type == 1 && !settings.inner) {
  9401. walk(node);
  9402. } else {
  9403. handlers[11](node);
  9404. }
  9405. return writer.getContent();
  9406. };
  9407. };
  9408. });
  9409. // Included from: js/tinymce/classes/dom/Serializer.js
  9410. /**
  9411. * Serializer.js
  9412. *
  9413. * Copyright, Moxiecode Systems AB
  9414. * Released under LGPL License.
  9415. *
  9416. * License: http://www.tinymce.com/license
  9417. * Contributing: http://www.tinymce.com/contributing
  9418. */
  9419. /**
  9420. * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
  9421. * more details and examples on how to use this class.
  9422. *
  9423. * @class tinymce.dom.Serializer
  9424. */
  9425. define("tinymce/dom/Serializer", [
  9426. "tinymce/dom/DOMUtils",
  9427. "tinymce/html/DomParser",
  9428. "tinymce/html/Entities",
  9429. "tinymce/html/Serializer",
  9430. "tinymce/html/Node",
  9431. "tinymce/html/Schema",
  9432. "tinymce/Env",
  9433. "tinymce/util/Tools"
  9434. ], function(DOMUtils, DomParser, Entities, Serializer, Node, Schema, Env, Tools) {
  9435. var each = Tools.each, trim = Tools.trim;
  9436. var DOM = DOMUtils.DOM;
  9437. /**
  9438. * Constructs a new DOM serializer class.
  9439. *
  9440. * @constructor
  9441. * @method Serializer
  9442. * @param {Object} settings Serializer settings object.
  9443. * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
  9444. */
  9445. return function(settings, editor) {
  9446. var dom, schema, htmlParser;
  9447. if (editor) {
  9448. dom = editor.dom;
  9449. schema = editor.schema;
  9450. }
  9451. // Default DOM and Schema if they are undefined
  9452. dom = dom || DOM;
  9453. schema = schema || new Schema(settings);
  9454. settings.entity_encoding = settings.entity_encoding || 'named';
  9455. settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
  9456. htmlParser = new DomParser(settings, schema);
  9457. // Convert tabindex back to elements when serializing contents
  9458. htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
  9459. var i = nodes.length, node;
  9460. while (i--) {
  9461. node = nodes[i];
  9462. node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
  9463. node.attr(name, null);
  9464. }
  9465. });
  9466. // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
  9467. htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
  9468. var i = nodes.length, node, value, internalName = 'data-mce-' + name;
  9469. var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
  9470. while (i--) {
  9471. node = nodes[i];
  9472. value = node.attributes.map[internalName];
  9473. if (value !== undef) {
  9474. // Set external name to internal value and remove internal
  9475. node.attr(name, value.length > 0 ? value : null);
  9476. node.attr(internalName, null);
  9477. } else {
  9478. // No internal attribute found then convert the value we have in the DOM
  9479. value = node.attributes.map[name];
  9480. if (name === "style") {
  9481. value = dom.serializeStyle(dom.parseStyle(value), node.name);
  9482. } else if (urlConverter) {
  9483. value = urlConverter.call(urlConverterScope, value, name, node.name);
  9484. }
  9485. node.attr(name, value.length > 0 ? value : null);
  9486. }
  9487. }
  9488. });
  9489. // Remove internal classes mceItem<..> or mceSelected
  9490. htmlParser.addAttributeFilter('class', function(nodes) {
  9491. var i = nodes.length, node, value;
  9492. while (i--) {
  9493. node = nodes[i];
  9494. value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
  9495. node.attr('class', value.length > 0 ? value : null);
  9496. }
  9497. });
  9498. // Remove bookmark elements
  9499. htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
  9500. var i = nodes.length, node;
  9501. while (i--) {
  9502. node = nodes[i];
  9503. if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
  9504. node.remove();
  9505. }
  9506. }
  9507. });
  9508. // Remove expando attributes
  9509. htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name) {
  9510. var i = nodes.length;
  9511. while (i--) {
  9512. nodes[i].attr(name, null);
  9513. }
  9514. });
  9515. htmlParser.addNodeFilter('noscript', function(nodes) {
  9516. var i = nodes.length, node;
  9517. while (i--) {
  9518. node = nodes[i].firstChild;
  9519. if (node) {
  9520. node.value = Entities.decode(node.value);
  9521. }
  9522. }
  9523. });
  9524. // Force script into CDATA sections and remove the mce- prefix also add comments around styles
  9525. htmlParser.addNodeFilter('script,style', function(nodes, name) {
  9526. var i = nodes.length, node, value;
  9527. function trim(value) {
  9528. /*jshint maxlen:255 */
  9529. /*eslint max-len:0 */
  9530. return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
  9531. .replace(/^[\r\n]*|[\r\n]*$/g, '')
  9532. .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
  9533. .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
  9534. }
  9535. while (i--) {
  9536. node = nodes[i];
  9537. value = node.firstChild ? node.firstChild.value : '';
  9538. if (name === "script") {
  9539. // Remove mce- prefix from script elements and remove default text/javascript mime type (HTML5)
  9540. var type = (node.attr('type') || 'text/javascript').replace(/^mce\-/, '');
  9541. node.attr('type', type === 'text/javascript' ? null : type);
  9542. if (value.length > 0) {
  9543. node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
  9544. }
  9545. } else {
  9546. if (value.length > 0) {
  9547. node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
  9548. }
  9549. }
  9550. }
  9551. });
  9552. // Convert comments to cdata and handle protected comments
  9553. htmlParser.addNodeFilter('#comment', function(nodes) {
  9554. var i = nodes.length, node;
  9555. while (i--) {
  9556. node = nodes[i];
  9557. if (node.value.indexOf('[CDATA[') === 0) {
  9558. node.name = '#cdata';
  9559. node.type = 4;
  9560. node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
  9561. } else if (node.value.indexOf('mce:protected ') === 0) {
  9562. node.name = "#text";
  9563. node.type = 3;
  9564. node.raw = true;
  9565. node.value = unescape(node.value).substr(14);
  9566. }
  9567. }
  9568. });
  9569. htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
  9570. var i = nodes.length, node;
  9571. while (i--) {
  9572. node = nodes[i];
  9573. if (node.type === 7) {
  9574. node.remove();
  9575. } else if (node.type === 1) {
  9576. if (name === "input" && !("type" in node.attributes.map)) {
  9577. node.attr('type', 'text');
  9578. }
  9579. }
  9580. }
  9581. });
  9582. // Fix list elements, TODO: Replace this later
  9583. if (settings.fix_list_elements) {
  9584. htmlParser.addNodeFilter('ul,ol', function(nodes) {
  9585. var i = nodes.length, node, parentNode;
  9586. while (i--) {
  9587. node = nodes[i];
  9588. parentNode = node.parent;
  9589. if (parentNode.name === 'ul' || parentNode.name === 'ol') {
  9590. if (node.prev && node.prev.name === 'li') {
  9591. node.prev.append(node);
  9592. }
  9593. }
  9594. }
  9595. });
  9596. }
  9597. // Remove internal data attributes
  9598. htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,data-mce-selected', function(nodes, name) {
  9599. var i = nodes.length;
  9600. while (i--) {
  9601. nodes[i].attr(name, null);
  9602. }
  9603. });
  9604. // Return public methods
  9605. return {
  9606. /**
  9607. * Schema instance that was used to when the Serializer was constructed.
  9608. *
  9609. * @field {tinymce.html.Schema} schema
  9610. */
  9611. schema: schema,
  9612. /**
  9613. * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
  9614. * and then execute the callback ones it has finished parsing the document.
  9615. *
  9616. * @example
  9617. * parser.addNodeFilter('p,h1', function(nodes, name) {
  9618. * for (var i = 0; i < nodes.length; i++) {
  9619. * console.log(nodes[i].name);
  9620. * }
  9621. * });
  9622. * @method addNodeFilter
  9623. * @method {String} name Comma separated list of nodes to collect.
  9624. * @param {function} callback Callback function to execute once it has collected nodes.
  9625. */
  9626. addNodeFilter: htmlParser.addNodeFilter,
  9627. /**
  9628. * Adds a attribute filter function to the parser used by the serializer, the parser will
  9629. * collect nodes that has the specified attributes
  9630. * and then execute the callback ones it has finished parsing the document.
  9631. *
  9632. * @example
  9633. * parser.addAttributeFilter('src,href', function(nodes, name) {
  9634. * for (var i = 0; i < nodes.length; i++) {
  9635. * console.log(nodes[i].name);
  9636. * }
  9637. * });
  9638. * @method addAttributeFilter
  9639. * @method {String} name Comma separated list of nodes to collect.
  9640. * @param {function} callback Callback function to execute once it has collected nodes.
  9641. */
  9642. addAttributeFilter: htmlParser.addAttributeFilter,
  9643. /**
  9644. * Serializes the specified browser DOM node into a HTML string.
  9645. *
  9646. * @method serialize
  9647. * @param {DOMNode} node DOM node to serialize.
  9648. * @param {Object} args Arguments option that gets passed to event handlers.
  9649. */
  9650. serialize: function(node, args) {
  9651. var self = this, impl, doc, oldDoc, htmlSerializer, content;
  9652. // Explorer won't clone contents of script and style and the
  9653. // selected index of select elements are cleared on a clone operation.
  9654. if (Env.ie && dom.select('script,style,select,map').length > 0) {
  9655. content = node.innerHTML;
  9656. node = node.cloneNode(false);
  9657. dom.setHTML(node, content);
  9658. } else {
  9659. node = node.cloneNode(true);
  9660. }
  9661. // Nodes needs to be attached to something in WebKit/Opera
  9662. // This fix will make DOM ranges and make Sizzle happy!
  9663. impl = node.ownerDocument.implementation;
  9664. if (impl.createHTMLDocument) {
  9665. // Create an empty HTML document
  9666. doc = impl.createHTMLDocument("");
  9667. // Add the element or it's children if it's a body element to the new document
  9668. each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
  9669. doc.body.appendChild(doc.importNode(node, true));
  9670. });
  9671. // Grab first child or body element for serialization
  9672. if (node.nodeName != 'BODY') {
  9673. node = doc.body.firstChild;
  9674. } else {
  9675. node = doc.body;
  9676. }
  9677. // set the new document in DOMUtils so createElement etc works
  9678. oldDoc = dom.doc;
  9679. dom.doc = doc;
  9680. }
  9681. args = args || {};
  9682. args.format = args.format || 'html';
  9683. // Don't wrap content if we want selected html
  9684. if (args.selection) {
  9685. args.forced_root_block = '';
  9686. }
  9687. // Pre process
  9688. if (!args.no_events) {
  9689. args.node = node;
  9690. self.onPreProcess(args);
  9691. }
  9692. // Setup serializer
  9693. htmlSerializer = new Serializer(settings, schema);
  9694. // Parse and serialize HTML
  9695. args.content = htmlSerializer.serialize(
  9696. htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
  9697. );
  9698. // Replace all BOM characters for now until we can find a better solution
  9699. if (!args.cleanup) {
  9700. args.content = args.content.replace(/\uFEFF/g, '');
  9701. }
  9702. // Post process
  9703. if (!args.no_events) {
  9704. self.onPostProcess(args);
  9705. }
  9706. // Restore the old document if it was changed
  9707. if (oldDoc) {
  9708. dom.doc = oldDoc;
  9709. }
  9710. args.node = null;
  9711. return args.content;
  9712. },
  9713. /**
  9714. * Adds valid elements rules to the serializers schema instance this enables you to specify things
  9715. * like what elements should be outputted and what attributes specific elements might have.
  9716. * Consult the Wiki for more details on this format.
  9717. *
  9718. * @method addRules
  9719. * @param {String} rules Valid elements rules string to add to schema.
  9720. */
  9721. addRules: function(rules) {
  9722. schema.addValidElements(rules);
  9723. },
  9724. /**
  9725. * Sets the valid elements rules to the serializers schema instance this enables you to specify things
  9726. * like what elements should be outputted and what attributes specific elements might have.
  9727. * Consult the Wiki for more details on this format.
  9728. *
  9729. * @method setRules
  9730. * @param {String} rules Valid elements rules string.
  9731. */
  9732. setRules: function(rules) {
  9733. schema.setValidElements(rules);
  9734. },
  9735. onPreProcess: function(args) {
  9736. if (editor) {
  9737. editor.fire('PreProcess', args);
  9738. }
  9739. },
  9740. onPostProcess: function(args) {
  9741. if (editor) {
  9742. editor.fire('PostProcess', args);
  9743. }
  9744. }
  9745. };
  9746. };
  9747. });
  9748. // Included from: js/tinymce/classes/dom/TridentSelection.js
  9749. /**
  9750. * TridentSelection.js
  9751. *
  9752. * Copyright, Moxiecode Systems AB
  9753. * Released under LGPL License.
  9754. *
  9755. * License: http://www.tinymce.com/license
  9756. * Contributing: http://www.tinymce.com/contributing
  9757. */
  9758. /**
  9759. * Selection class for old explorer versions. This one fakes the
  9760. * native selection object available on modern browsers.
  9761. *
  9762. * @class tinymce.dom.TridentSelection
  9763. */
  9764. define("tinymce/dom/TridentSelection", [], function() {
  9765. function Selection(selection) {
  9766. var self = this, dom = selection.dom, FALSE = false;
  9767. function getPosition(rng, start) {
  9768. var checkRng, startIndex = 0, endIndex, inside,
  9769. children, child, offset, index, position = -1, parent;
  9770. // Setup test range, collapse it and get the parent
  9771. checkRng = rng.duplicate();
  9772. checkRng.collapse(start);
  9773. parent = checkRng.parentElement();
  9774. // Check if the selection is within the right document
  9775. if (parent.ownerDocument !== selection.dom.doc) {
  9776. return;
  9777. }
  9778. // IE will report non editable elements as it's parent so look for an editable one
  9779. while (parent.contentEditable === "false") {
  9780. parent = parent.parentNode;
  9781. }
  9782. // If parent doesn't have any children then return that we are inside the element
  9783. if (!parent.hasChildNodes()) {
  9784. return {node: parent, inside: 1};
  9785. }
  9786. // Setup node list and endIndex
  9787. children = parent.children;
  9788. endIndex = children.length - 1;
  9789. // Perform a binary search for the position
  9790. while (startIndex <= endIndex) {
  9791. index = Math.floor((startIndex + endIndex) / 2);
  9792. // Move selection to node and compare the ranges
  9793. child = children[index];
  9794. checkRng.moveToElementText(child);
  9795. position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
  9796. // Before/after or an exact match
  9797. if (position > 0) {
  9798. endIndex = index - 1;
  9799. } else if (position < 0) {
  9800. startIndex = index + 1;
  9801. } else {
  9802. return {node: child};
  9803. }
  9804. }
  9805. // Check if child position is before or we didn't find a position
  9806. if (position < 0) {
  9807. // No element child was found use the parent element and the offset inside that
  9808. if (!child) {
  9809. checkRng.moveToElementText(parent);
  9810. checkRng.collapse(true);
  9811. child = parent;
  9812. inside = true;
  9813. } else {
  9814. checkRng.collapse(false);
  9815. }
  9816. // Walk character by character in text node until we hit the selected range endpoint,
  9817. // hit the end of document or parent isn't the right one
  9818. // We need to walk char by char since rng.text or rng.htmlText will trim line endings
  9819. offset = 0;
  9820. while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  9821. if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
  9822. break;
  9823. }
  9824. offset++;
  9825. }
  9826. } else {
  9827. // Child position is after the selection endpoint
  9828. checkRng.collapse(true);
  9829. // Walk character by character in text node until we hit the selected range endpoint, hit
  9830. // the end of document or parent isn't the right one
  9831. offset = 0;
  9832. while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
  9833. if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
  9834. break;
  9835. }
  9836. offset++;
  9837. }
  9838. }
  9839. return {node: child, position: position, offset: offset, inside: inside};
  9840. }
  9841. // Returns a W3C DOM compatible range object by using the IE Range API
  9842. function getRange() {
  9843. var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
  9844. // If selection is outside the current document just return an empty range
  9845. element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
  9846. if (element.ownerDocument != dom.doc) {
  9847. return domRange;
  9848. }
  9849. collapsed = selection.isCollapsed();
  9850. // Handle control selection
  9851. if (ieRange.item) {
  9852. domRange.setStart(element.parentNode, dom.nodeIndex(element));
  9853. domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
  9854. return domRange;
  9855. }
  9856. function findEndPoint(start) {
  9857. var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
  9858. container = endPoint.node;
  9859. offset = endPoint.offset;
  9860. if (endPoint.inside && !container.hasChildNodes()) {
  9861. domRange[start ? 'setStart' : 'setEnd'](container, 0);
  9862. return;
  9863. }
  9864. if (offset === undef) {
  9865. domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
  9866. return;
  9867. }
  9868. if (endPoint.position < 0) {
  9869. sibling = endPoint.inside ? container.firstChild : container.nextSibling;
  9870. if (!sibling) {
  9871. domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
  9872. return;
  9873. }
  9874. if (!offset) {
  9875. if (sibling.nodeType == 3) {
  9876. domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
  9877. } else {
  9878. domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
  9879. }
  9880. return;
  9881. }
  9882. // Find the text node and offset
  9883. while (sibling) {
  9884. nodeValue = sibling.nodeValue;
  9885. textNodeOffset += nodeValue.length;
  9886. // We are at or passed the position we where looking for
  9887. if (textNodeOffset >= offset) {
  9888. container = sibling;
  9889. textNodeOffset -= offset;
  9890. textNodeOffset = nodeValue.length - textNodeOffset;
  9891. break;
  9892. }
  9893. sibling = sibling.nextSibling;
  9894. }
  9895. } else {
  9896. // Find the text node and offset
  9897. sibling = container.previousSibling;
  9898. if (!sibling) {
  9899. return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
  9900. }
  9901. // If there isn't any text to loop then use the first position
  9902. if (!offset) {
  9903. if (container.nodeType == 3) {
  9904. domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
  9905. } else {
  9906. domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
  9907. }
  9908. return;
  9909. }
  9910. while (sibling) {
  9911. textNodeOffset += sibling.nodeValue.length;
  9912. // We are at or passed the position we where looking for
  9913. if (textNodeOffset >= offset) {
  9914. container = sibling;
  9915. textNodeOffset -= offset;
  9916. break;
  9917. }
  9918. sibling = sibling.previousSibling;
  9919. }
  9920. }
  9921. domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
  9922. }
  9923. try {
  9924. // Find start point
  9925. findEndPoint(true);
  9926. // Find end point if needed
  9927. if (!collapsed) {
  9928. findEndPoint();
  9929. }
  9930. } catch (ex) {
  9931. // IE has a nasty bug where text nodes might throw "invalid argument" when you
  9932. // access the nodeValue or other properties of text nodes. This seems to happend when
  9933. // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
  9934. if (ex.number == -2147024809) {
  9935. // Get the current selection
  9936. bookmark = self.getBookmark(2);
  9937. // Get start element
  9938. tmpRange = ieRange.duplicate();
  9939. tmpRange.collapse(true);
  9940. element = tmpRange.parentElement();
  9941. // Get end element
  9942. if (!collapsed) {
  9943. tmpRange = ieRange.duplicate();
  9944. tmpRange.collapse(false);
  9945. element2 = tmpRange.parentElement();
  9946. element2.innerHTML = element2.innerHTML;
  9947. }
  9948. // Remove the broken elements
  9949. element.innerHTML = element.innerHTML;
  9950. // Restore the selection
  9951. self.moveToBookmark(bookmark);
  9952. // Since the range has moved we need to re-get it
  9953. ieRange = selection.getRng();
  9954. // Find start point
  9955. findEndPoint(true);
  9956. // Find end point if needed
  9957. if (!collapsed) {
  9958. findEndPoint();
  9959. }
  9960. } else {
  9961. throw ex; // Throw other errors
  9962. }
  9963. }
  9964. return domRange;
  9965. }
  9966. this.getBookmark = function(type) {
  9967. var rng = selection.getRng(), bookmark = {};
  9968. function getIndexes(node) {
  9969. var parent, root, children, i, indexes = [];
  9970. parent = node.parentNode;
  9971. root = dom.getRoot().parentNode;
  9972. while (parent != root && parent.nodeType !== 9) {
  9973. children = parent.children;
  9974. i = children.length;
  9975. while (i--) {
  9976. if (node === children[i]) {
  9977. indexes.push(i);
  9978. break;
  9979. }
  9980. }
  9981. node = parent;
  9982. parent = parent.parentNode;
  9983. }
  9984. return indexes;
  9985. }
  9986. function getBookmarkEndPoint(start) {
  9987. var position;
  9988. position = getPosition(rng, start);
  9989. if (position) {
  9990. return {
  9991. position: position.position,
  9992. offset: position.offset,
  9993. indexes: getIndexes(position.node),
  9994. inside: position.inside
  9995. };
  9996. }
  9997. }
  9998. // Non ubstructive bookmark
  9999. if (type === 2) {
  10000. // Handle text selection
  10001. if (!rng.item) {
  10002. bookmark.start = getBookmarkEndPoint(true);
  10003. if (!selection.isCollapsed()) {
  10004. bookmark.end = getBookmarkEndPoint();
  10005. }
  10006. } else {
  10007. bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
  10008. }
  10009. }
  10010. return bookmark;
  10011. };
  10012. this.moveToBookmark = function(bookmark) {
  10013. var rng, body = dom.doc.body;
  10014. function resolveIndexes(indexes) {
  10015. var node, i, idx, children;
  10016. node = dom.getRoot();
  10017. for (i = indexes.length - 1; i >= 0; i--) {
  10018. children = node.children;
  10019. idx = indexes[i];
  10020. if (idx <= children.length - 1) {
  10021. node = children[idx];
  10022. }
  10023. }
  10024. return node;
  10025. }
  10026. function setBookmarkEndPoint(start) {
  10027. var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
  10028. if (endPoint) {
  10029. moveLeft = endPoint.position > 0;
  10030. moveRng = body.createTextRange();
  10031. moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
  10032. offset = endPoint.offset;
  10033. if (offset !== undef) {
  10034. moveRng.collapse(endPoint.inside || moveLeft);
  10035. moveRng.moveStart('character', moveLeft ? -offset : offset);
  10036. } else {
  10037. moveRng.collapse(start);
  10038. }
  10039. rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
  10040. if (start) {
  10041. rng.collapse(true);
  10042. }
  10043. }
  10044. }
  10045. if (bookmark.start) {
  10046. if (bookmark.start.ctrl) {
  10047. rng = body.createControlRange();
  10048. rng.addElement(resolveIndexes(bookmark.start.indexes));
  10049. rng.select();
  10050. } else {
  10051. rng = body.createTextRange();
  10052. setBookmarkEndPoint(true);
  10053. setBookmarkEndPoint();
  10054. rng.select();
  10055. }
  10056. }
  10057. };
  10058. this.addRange = function(rng) {
  10059. var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
  10060. doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
  10061. function setEndPoint(start) {
  10062. var container, offset, marker, tmpRng, nodes;
  10063. marker = dom.create('a');
  10064. container = start ? startContainer : endContainer;
  10065. offset = start ? startOffset : endOffset;
  10066. tmpRng = ieRng.duplicate();
  10067. if (container == doc || container == doc.documentElement) {
  10068. container = body;
  10069. offset = 0;
  10070. }
  10071. if (container.nodeType == 3) {
  10072. container.parentNode.insertBefore(marker, container);
  10073. tmpRng.moveToElementText(marker);
  10074. tmpRng.moveStart('character', offset);
  10075. dom.remove(marker);
  10076. ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  10077. } else {
  10078. nodes = container.childNodes;
  10079. if (nodes.length) {
  10080. if (offset >= nodes.length) {
  10081. dom.insertAfter(marker, nodes[nodes.length - 1]);
  10082. } else {
  10083. container.insertBefore(marker, nodes[offset]);
  10084. }
  10085. tmpRng.moveToElementText(marker);
  10086. } else if (container.canHaveHTML) {
  10087. // Empty node selection for example <div>|</div>
  10088. // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
  10089. container.innerHTML = '<span>&#xFEFF;</span>';
  10090. marker = container.firstChild;
  10091. tmpRng.moveToElementText(marker);
  10092. tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
  10093. }
  10094. ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
  10095. dom.remove(marker);
  10096. }
  10097. }
  10098. // Setup some shorter versions
  10099. startContainer = rng.startContainer;
  10100. startOffset = rng.startOffset;
  10101. endContainer = rng.endContainer;
  10102. endOffset = rng.endOffset;
  10103. ieRng = body.createTextRange();
  10104. // If single element selection then try making a control selection out of it
  10105. if (startContainer == endContainer && startContainer.nodeType == 1) {
  10106. // Trick to place the caret inside an empty block element like <p></p>
  10107. if (startOffset == endOffset && !startContainer.hasChildNodes()) {
  10108. if (startContainer.canHaveHTML) {
  10109. // Check if previous sibling is an empty block if it is then we need to render it
  10110. // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
  10111. // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
  10112. sibling = startContainer.previousSibling;
  10113. if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
  10114. sibling.innerHTML = '&#xFEFF;';
  10115. } else {
  10116. sibling = null;
  10117. }
  10118. startContainer.innerHTML = '<span>&#xFEFF;</span><span>&#xFEFF;</span>';
  10119. ieRng.moveToElementText(startContainer.lastChild);
  10120. ieRng.select();
  10121. dom.doc.selection.clear();
  10122. startContainer.innerHTML = '';
  10123. if (sibling) {
  10124. sibling.innerHTML = '';
  10125. }
  10126. return;
  10127. } else {
  10128. startOffset = dom.nodeIndex(startContainer);
  10129. startContainer = startContainer.parentNode;
  10130. }
  10131. }
  10132. if (startOffset == endOffset - 1) {
  10133. try {
  10134. ctrlElm = startContainer.childNodes[startOffset];
  10135. ctrlRng = body.createControlRange();
  10136. ctrlRng.addElement(ctrlElm);
  10137. ctrlRng.select();
  10138. // Check if the range produced is on the correct element and is a control range
  10139. // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
  10140. nativeRng = selection.getRng();
  10141. if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
  10142. return;
  10143. }
  10144. } catch (ex) {
  10145. // Ignore
  10146. }
  10147. }
  10148. }
  10149. // Set start/end point of selection
  10150. setEndPoint(true);
  10151. setEndPoint();
  10152. // Select the new range and scroll it into view
  10153. ieRng.select();
  10154. };
  10155. // Expose range method
  10156. this.getRangeAt = getRange;
  10157. }
  10158. return Selection;
  10159. });
  10160. // Included from: js/tinymce/classes/util/VK.js
  10161. /**
  10162. * VK.js
  10163. *
  10164. * Copyright, Moxiecode Systems AB
  10165. * Released under LGPL License.
  10166. *
  10167. * License: http://www.tinymce.com/license
  10168. * Contributing: http://www.tinymce.com/contributing
  10169. */
  10170. /**
  10171. * This file exposes a set of the common KeyCodes for use. Please grow it as needed.
  10172. */
  10173. define("tinymce/util/VK", [
  10174. "tinymce/Env"
  10175. ], function(Env) {
  10176. return {
  10177. BACKSPACE: 8,
  10178. DELETE: 46,
  10179. DOWN: 40,
  10180. ENTER: 13,
  10181. LEFT: 37,
  10182. RIGHT: 39,
  10183. SPACEBAR: 32,
  10184. TAB: 9,
  10185. UP: 38,
  10186. modifierPressed: function(e) {
  10187. return e.shiftKey || e.ctrlKey || e.altKey;
  10188. },
  10189. metaKeyPressed: function(e) {
  10190. // Check if ctrl or meta key is pressed also check if alt is false for Polish users
  10191. return (Env.mac ? e.metaKey : e.ctrlKey) && !e.altKey;
  10192. }
  10193. };
  10194. });
  10195. // Included from: js/tinymce/classes/dom/ControlSelection.js
  10196. /**
  10197. * ControlSelection.js
  10198. *
  10199. * Copyright, Moxiecode Systems AB
  10200. * Released under LGPL License.
  10201. *
  10202. * License: http://www.tinymce.com/license
  10203. * Contributing: http://www.tinymce.com/contributing
  10204. */
  10205. /**
  10206. * This class handles control selection of elements. Controls are elements
  10207. * that can be resized and needs to be selected as a whole. It adds custom resize handles
  10208. * to all browser engines that support properly disabling the built in resize logic.
  10209. *
  10210. * @class tinymce.dom.ControlSelection
  10211. */
  10212. define("tinymce/dom/ControlSelection", [
  10213. "tinymce/util/VK",
  10214. "tinymce/util/Tools",
  10215. "tinymce/Env"
  10216. ], function(VK, Tools, Env) {
  10217. return function(selection, editor) {
  10218. var dom = editor.dom, each = Tools.each;
  10219. var selectedElm, selectedElmGhost, resizeHandles, selectedHandle, lastMouseDownEvent;
  10220. var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
  10221. var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
  10222. // Details about each resize handle how to scale etc
  10223. resizeHandles = {
  10224. // Name: x multiplier, y multiplier, delta size x, delta size y
  10225. n: [0.5, 0, 0, -1],
  10226. e: [1, 0.5, 1, 0],
  10227. s: [0.5, 1, 0, 1],
  10228. w: [0, 0.5, -1, 0],
  10229. nw: [0, 0, -1, -1],
  10230. ne: [1, 0, 1, -1],
  10231. se: [1, 1, 1, 1],
  10232. sw: [0, 1, -1, 1]
  10233. };
  10234. // Add CSS for resize handles, cloned element and selected
  10235. var rootClass = '.mce-content-body';
  10236. editor.contentStyles.push(
  10237. rootClass + ' div.mce-resizehandle {' +
  10238. 'position: absolute;' +
  10239. 'border: 1px solid black;' +
  10240. 'background: #FFF;' +
  10241. 'width: 5px;' +
  10242. 'height: 5px;' +
  10243. 'z-index: 10000' +
  10244. '}' +
  10245. rootClass + ' .mce-resizehandle:hover {' +
  10246. 'background: #000' +
  10247. '}' +
  10248. rootClass + ' img[data-mce-selected], hr[data-mce-selected] {' +
  10249. 'outline: 1px solid black;' +
  10250. 'resize: none' + // Have been talks about implementing this in browsers
  10251. '}' +
  10252. rootClass + ' .mce-clonedresizable {' +
  10253. 'position: absolute;' +
  10254. (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
  10255. 'opacity: .5;' +
  10256. 'filter: alpha(opacity=50);' +
  10257. 'z-index: 10000' +
  10258. '}'
  10259. );
  10260. function isResizable(elm) {
  10261. var selector = editor.settings.object_resizing;
  10262. if (selector === false || Env.iOS) {
  10263. return false;
  10264. }
  10265. if (typeof selector != 'string') {
  10266. selector = 'table,img,div';
  10267. }
  10268. if (elm.getAttribute('data-mce-resize') === 'false') {
  10269. return false;
  10270. }
  10271. return editor.dom.is(elm, selector);
  10272. }
  10273. function resizeGhostElement(e) {
  10274. var deltaX, deltaY;
  10275. // Calc new width/height
  10276. deltaX = e.screenX - startX;
  10277. deltaY = e.screenY - startY;
  10278. // Calc new size
  10279. width = deltaX * selectedHandle[2] + startW;
  10280. height = deltaY * selectedHandle[3] + startH;
  10281. // Never scale down lower than 5 pixels
  10282. width = width < 5 ? 5 : width;
  10283. height = height < 5 ? 5 : height;
  10284. // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
  10285. if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
  10286. width = Math.round(height / ratio);
  10287. height = Math.round(width * ratio);
  10288. }
  10289. // Update ghost size
  10290. dom.setStyles(selectedElmGhost, {
  10291. width: width,
  10292. height: height
  10293. });
  10294. // Update ghost X position if needed
  10295. if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
  10296. dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
  10297. }
  10298. // Update ghost Y position if needed
  10299. if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
  10300. dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
  10301. }
  10302. if (!resizeStarted) {
  10303. editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
  10304. resizeStarted = true;
  10305. }
  10306. }
  10307. function endGhostResize() {
  10308. resizeStarted = false;
  10309. function setSizeProp(name, value) {
  10310. if (value) {
  10311. // Resize by using style or attribute
  10312. if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
  10313. dom.setStyle(selectedElm, name, value);
  10314. } else {
  10315. dom.setAttrib(selectedElm, name, value);
  10316. }
  10317. }
  10318. }
  10319. // Set width/height properties
  10320. setSizeProp('width', width);
  10321. setSizeProp('height', height);
  10322. dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
  10323. dom.unbind(editableDoc, 'mouseup', endGhostResize);
  10324. if (rootDocument != editableDoc) {
  10325. dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
  10326. dom.unbind(rootDocument, 'mouseup', endGhostResize);
  10327. }
  10328. // Remove ghost and update resize handle positions
  10329. dom.remove(selectedElmGhost);
  10330. if (!isIE || selectedElm.nodeName == "TABLE") {
  10331. showResizeRect(selectedElm);
  10332. }
  10333. editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
  10334. editor.nodeChanged();
  10335. }
  10336. function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
  10337. var position, targetWidth, targetHeight, e, rect, offsetParent = editor.getBody();
  10338. unbindResizeHandleEvents();
  10339. // Get position and size of target
  10340. position = dom.getPos(targetElm, offsetParent);
  10341. selectedElmX = position.x;
  10342. selectedElmY = position.y;
  10343. rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
  10344. targetWidth = rect.width || (rect.right - rect.left);
  10345. targetHeight = rect.height || (rect.bottom - rect.top);
  10346. // Reset width/height if user selects a new image/table
  10347. if (selectedElm != targetElm) {
  10348. detachResizeStartListener();
  10349. selectedElm = targetElm;
  10350. width = height = 0;
  10351. }
  10352. // Makes it possible to disable resizing
  10353. e = editor.fire('ObjectSelected', {target: targetElm});
  10354. if (isResizable(targetElm) && !e.isDefaultPrevented()) {
  10355. each(resizeHandles, function(handle, name) {
  10356. var handleElm, handlerContainerElm;
  10357. function startDrag(e) {
  10358. startX = e.screenX;
  10359. startY = e.screenY;
  10360. startW = selectedElm.clientWidth;
  10361. startH = selectedElm.clientHeight;
  10362. ratio = startH / startW;
  10363. selectedHandle = handle;
  10364. selectedElmGhost = selectedElm.cloneNode(true);
  10365. dom.addClass(selectedElmGhost, 'mce-clonedresizable');
  10366. selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
  10367. selectedElmGhost.unSelectabe = true;
  10368. dom.setStyles(selectedElmGhost, {
  10369. left: selectedElmX,
  10370. top: selectedElmY,
  10371. margin: 0
  10372. });
  10373. selectedElmGhost.removeAttribute('data-mce-selected');
  10374. editor.getBody().appendChild(selectedElmGhost);
  10375. dom.bind(editableDoc, 'mousemove', resizeGhostElement);
  10376. dom.bind(editableDoc, 'mouseup', endGhostResize);
  10377. if (rootDocument != editableDoc) {
  10378. dom.bind(rootDocument, 'mousemove', resizeGhostElement);
  10379. dom.bind(rootDocument, 'mouseup', endGhostResize);
  10380. }
  10381. }
  10382. if (mouseDownHandleName) {
  10383. // Drag started by IE native resizestart
  10384. if (name == mouseDownHandleName) {
  10385. startDrag(mouseDownEvent);
  10386. }
  10387. return;
  10388. }
  10389. // Get existing or render resize handle
  10390. handleElm = dom.get('mceResizeHandle' + name);
  10391. if (!handleElm) {
  10392. handlerContainerElm = editor.getBody();
  10393. handleElm = dom.add(handlerContainerElm, 'div', {
  10394. id: 'mceResizeHandle' + name,
  10395. 'data-mce-bogus': true,
  10396. 'class': 'mce-resizehandle',
  10397. unselectable: true,
  10398. style: 'cursor:' + name + '-resize; margin:0; padding:0'
  10399. });
  10400. // Hides IE move layer cursor
  10401. // If we set it on Chrome we get this wounderful bug: #6725
  10402. if (Env.ie) {
  10403. handleElm.contentEditable = false;
  10404. }
  10405. } else {
  10406. dom.show(handleElm);
  10407. }
  10408. if (!handle.elm) {
  10409. dom.bind(handleElm, 'mousedown', function(e) {
  10410. e.stopImmediatePropagation();
  10411. e.preventDefault();
  10412. startDrag(e);
  10413. });
  10414. handle.elm = handleElm;
  10415. }
  10416. /*
  10417. var halfHandleW = handleElm.offsetWidth / 2;
  10418. var halfHandleH = handleElm.offsetHeight / 2;
  10419. // Position element
  10420. dom.setStyles(handleElm, {
  10421. left: Math.floor((targetWidth * handle[0] + selectedElmX) - halfHandleW + (handle[2] * halfHandleW)),
  10422. top: Math.floor((targetHeight * handle[1] + selectedElmY) - halfHandleH + (handle[3] * halfHandleH))
  10423. });
  10424. */
  10425. // Position element
  10426. dom.setStyles(handleElm, {
  10427. left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
  10428. top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
  10429. });
  10430. });
  10431. } else {
  10432. hideResizeRect();
  10433. }
  10434. selectedElm.setAttribute('data-mce-selected', '1');
  10435. }
  10436. function hideResizeRect() {
  10437. var name, handleElm;
  10438. unbindResizeHandleEvents();
  10439. if (selectedElm) {
  10440. selectedElm.removeAttribute('data-mce-selected');
  10441. }
  10442. for (name in resizeHandles) {
  10443. handleElm = dom.get('mceResizeHandle' + name);
  10444. if (handleElm) {
  10445. dom.unbind(handleElm);
  10446. dom.remove(handleElm);
  10447. }
  10448. }
  10449. }
  10450. function updateResizeRect(e) {
  10451. var controlElm;
  10452. function isChildOrEqual(node, parent) {
  10453. if (node) {
  10454. do {
  10455. if (node === parent) {
  10456. return true;
  10457. }
  10458. } while ((node = node.parentNode));
  10459. }
  10460. }
  10461. // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
  10462. each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
  10463. img.removeAttribute('data-mce-selected');
  10464. });
  10465. controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
  10466. controlElm = dom.getParent(controlElm, isIE ? 'table' : 'table,img,hr');
  10467. if (isChildOrEqual(controlElm, editor.getBody())) {
  10468. disableGeckoResize();
  10469. if (isChildOrEqual(selection.getStart(), controlElm) && isChildOrEqual(selection.getEnd(), controlElm)) {
  10470. if (!isIE || (controlElm != selection.getStart() && selection.getStart().nodeName !== 'IMG')) {
  10471. showResizeRect(controlElm);
  10472. return;
  10473. }
  10474. }
  10475. }
  10476. hideResizeRect();
  10477. }
  10478. function attachEvent(elm, name, func) {
  10479. if (elm && elm.attachEvent) {
  10480. elm.attachEvent('on' + name, func);
  10481. }
  10482. }
  10483. function detachEvent(elm, name, func) {
  10484. if (elm && elm.detachEvent) {
  10485. elm.detachEvent('on' + name, func);
  10486. }
  10487. }
  10488. function resizeNativeStart(e) {
  10489. var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
  10490. pos = target.getBoundingClientRect();
  10491. relativeX = lastMouseDownEvent.clientX - pos.left;
  10492. relativeY = lastMouseDownEvent.clientY - pos.top;
  10493. // Figure out what corner we are draging on
  10494. for (name in resizeHandles) {
  10495. corner = resizeHandles[name];
  10496. cornerX = target.offsetWidth * corner[0];
  10497. cornerY = target.offsetHeight * corner[1];
  10498. if (Math.abs(cornerX - relativeX) < 8 && Math.abs(cornerY - relativeY) < 8) {
  10499. selectedHandle = corner;
  10500. break;
  10501. }
  10502. }
  10503. // Remove native selection and let the magic begin
  10504. resizeStarted = true;
  10505. editor.getDoc().selection.empty();
  10506. showResizeRect(target, name, lastMouseDownEvent);
  10507. }
  10508. function nativeControlSelect(e) {
  10509. var target = e.srcElement;
  10510. if (target != selectedElm) {
  10511. detachResizeStartListener();
  10512. if (target.id.indexOf('mceResizeHandle') === 0) {
  10513. e.returnValue = false;
  10514. return;
  10515. }
  10516. if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
  10517. hideResizeRect();
  10518. selectedElm = target;
  10519. attachEvent(target, 'resizestart', resizeNativeStart);
  10520. }
  10521. }
  10522. }
  10523. function detachResizeStartListener() {
  10524. detachEvent(selectedElm, 'resizestart', resizeNativeStart);
  10525. }
  10526. function unbindResizeHandleEvents() {
  10527. for (var name in resizeHandles) {
  10528. var handle = resizeHandles[name];
  10529. if (handle.elm) {
  10530. dom.unbind(handle.elm);
  10531. delete handle.elm;
  10532. }
  10533. }
  10534. }
  10535. function disableGeckoResize() {
  10536. try {
  10537. // Disable object resizing on Gecko
  10538. editor.getDoc().execCommand('enableObjectResizing', false, false);
  10539. } catch (ex) {
  10540. // Ignore
  10541. }
  10542. }
  10543. function controlSelect(elm) {
  10544. var ctrlRng;
  10545. if (!isIE) {
  10546. return;
  10547. }
  10548. ctrlRng = editableDoc.body.createControlRange();
  10549. try {
  10550. ctrlRng.addElement(elm);
  10551. ctrlRng.select();
  10552. return true;
  10553. } catch (ex) {
  10554. // Ignore since the element can't be control selected for example a P tag
  10555. }
  10556. }
  10557. editor.on('init', function() {
  10558. if (isIE) {
  10559. // Hide the resize rect on resize and reselect the image
  10560. editor.on('ObjectResized', function(e) {
  10561. if (e.target.nodeName != 'TABLE') {
  10562. hideResizeRect();
  10563. controlSelect(e.target);
  10564. }
  10565. });
  10566. attachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
  10567. editor.on('mousedown', function(e) {
  10568. lastMouseDownEvent = e;
  10569. });
  10570. } else {
  10571. disableGeckoResize();
  10572. if (Env.ie >= 11) {
  10573. // TODO: Drag/drop doesn't work
  10574. editor.on('mouseup', function(e) {
  10575. var nodeName = e.target.nodeName;
  10576. if (/^(TABLE|IMG|HR)$/.test(nodeName)) {
  10577. editor.selection.select(e.target, nodeName == 'TABLE');
  10578. editor.nodeChanged();
  10579. }
  10580. });
  10581. editor.dom.bind(editor.getBody(), 'mscontrolselect', function(e) {
  10582. if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
  10583. e.preventDefault();
  10584. // This moves the selection from being a control selection to a text like selection like in WebKit #6753
  10585. // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
  10586. if (e.target.tagName == 'IMG') {
  10587. window.setTimeout(function() {
  10588. editor.selection.select(e.target);
  10589. }, 0);
  10590. }
  10591. }
  10592. });
  10593. }
  10594. }
  10595. editor.on('nodechange mousedown mouseup ResizeEditor', updateResizeRect);
  10596. // Update resize rect while typing in a table
  10597. editor.on('keydown keyup', function(e) {
  10598. if (selectedElm && selectedElm.nodeName == "TABLE") {
  10599. updateResizeRect(e);
  10600. }
  10601. });
  10602. // Hide rect on focusout since it would float on top of windows otherwise
  10603. //editor.on('focusout', hideResizeRect);
  10604. });
  10605. editor.on('remove', unbindResizeHandleEvents);
  10606. function destroy() {
  10607. selectedElm = selectedElmGhost = null;
  10608. if (isIE) {
  10609. detachResizeStartListener();
  10610. detachEvent(editor.getBody(), 'controlselect', nativeControlSelect);
  10611. }
  10612. }
  10613. return {
  10614. isResizable: isResizable,
  10615. showResizeRect: showResizeRect,
  10616. hideResizeRect: hideResizeRect,
  10617. updateResizeRect: updateResizeRect,
  10618. controlSelect: controlSelect,
  10619. destroy: destroy
  10620. };
  10621. };
  10622. });
  10623. // Included from: js/tinymce/classes/dom/RangeUtils.js
  10624. /**
  10625. * Range.js
  10626. *
  10627. * Copyright, Moxiecode Systems AB
  10628. * Released under LGPL License.
  10629. *
  10630. * License: http://www.tinymce.com/license
  10631. * Contributing: http://www.tinymce.com/contributing
  10632. */
  10633. /**
  10634. * RangeUtils
  10635. *
  10636. * @class tinymce.dom.RangeUtils
  10637. * @private
  10638. */
  10639. define("tinymce/dom/RangeUtils", [
  10640. "tinymce/util/Tools",
  10641. "tinymce/dom/TreeWalker"
  10642. ], function(Tools, TreeWalker) {
  10643. var each = Tools.each;
  10644. function RangeUtils(dom) {
  10645. /**
  10646. * Walks the specified range like object and executes the callback for each sibling collection it finds.
  10647. *
  10648. * @method walk
  10649. * @param {Object} rng Range like object.
  10650. * @param {function} callback Callback function to execute for each sibling collection.
  10651. */
  10652. this.walk = function(rng, callback) {
  10653. var startContainer = rng.startContainer,
  10654. startOffset = rng.startOffset,
  10655. endContainer = rng.endContainer,
  10656. endOffset = rng.endOffset,
  10657. ancestor, startPoint,
  10658. endPoint, node, parent, siblings, nodes;
  10659. // Handle table cell selection the table plugin enables
  10660. // you to fake select table cells and perform formatting actions on them
  10661. nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
  10662. if (nodes.length > 0) {
  10663. each(nodes, function(node) {
  10664. callback([node]);
  10665. });
  10666. return;
  10667. }
  10668. /**
  10669. * Excludes start/end text node if they are out side the range
  10670. *
  10671. * @private
  10672. * @param {Array} nodes Nodes to exclude items from.
  10673. * @return {Array} Array with nodes excluding the start/end container if needed.
  10674. */
  10675. function exclude(nodes) {
  10676. var node;
  10677. // First node is excluded
  10678. node = nodes[0];
  10679. if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
  10680. nodes.splice(0, 1);
  10681. }
  10682. // Last node is excluded
  10683. node = nodes[nodes.length - 1];
  10684. if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
  10685. nodes.splice(nodes.length - 1, 1);
  10686. }
  10687. return nodes;
  10688. }
  10689. /**
  10690. * Collects siblings
  10691. *
  10692. * @private
  10693. * @param {Node} node Node to collect siblings from.
  10694. * @param {String} name Name of the sibling to check for.
  10695. * @return {Array} Array of collected siblings.
  10696. */
  10697. function collectSiblings(node, name, end_node) {
  10698. var siblings = [];
  10699. for (; node && node != end_node; node = node[name]) {
  10700. siblings.push(node);
  10701. }
  10702. return siblings;
  10703. }
  10704. /**
  10705. * Find an end point this is the node just before the common ancestor root.
  10706. *
  10707. * @private
  10708. * @param {Node} node Node to start at.
  10709. * @param {Node} root Root/ancestor element to stop just before.
  10710. * @return {Node} Node just before the root element.
  10711. */
  10712. function findEndPoint(node, root) {
  10713. do {
  10714. if (node.parentNode == root) {
  10715. return node;
  10716. }
  10717. node = node.parentNode;
  10718. } while(node);
  10719. }
  10720. function walkBoundary(start_node, end_node, next) {
  10721. var siblingName = next ? 'nextSibling' : 'previousSibling';
  10722. for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
  10723. parent = node.parentNode;
  10724. siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
  10725. if (siblings.length) {
  10726. if (!next) {
  10727. siblings.reverse();
  10728. }
  10729. callback(exclude(siblings));
  10730. }
  10731. }
  10732. }
  10733. // If index based start position then resolve it
  10734. if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  10735. startContainer = startContainer.childNodes[startOffset];
  10736. }
  10737. // If index based end position then resolve it
  10738. if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  10739. endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
  10740. }
  10741. // Same container
  10742. if (startContainer == endContainer) {
  10743. return callback(exclude([startContainer]));
  10744. }
  10745. // Find common ancestor and end points
  10746. ancestor = dom.findCommonAncestor(startContainer, endContainer);
  10747. // Process left side
  10748. for (node = startContainer; node; node = node.parentNode) {
  10749. if (node === endContainer) {
  10750. return walkBoundary(startContainer, ancestor, true);
  10751. }
  10752. if (node === ancestor) {
  10753. break;
  10754. }
  10755. }
  10756. // Process right side
  10757. for (node = endContainer; node; node = node.parentNode) {
  10758. if (node === startContainer) {
  10759. return walkBoundary(endContainer, ancestor);
  10760. }
  10761. if (node === ancestor) {
  10762. break;
  10763. }
  10764. }
  10765. // Find start/end point
  10766. startPoint = findEndPoint(startContainer, ancestor) || startContainer;
  10767. endPoint = findEndPoint(endContainer, ancestor) || endContainer;
  10768. // Walk left leaf
  10769. walkBoundary(startContainer, startPoint, true);
  10770. // Walk the middle from start to end point
  10771. siblings = collectSiblings(
  10772. startPoint == startContainer ? startPoint : startPoint.nextSibling,
  10773. 'nextSibling',
  10774. endPoint == endContainer ? endPoint.nextSibling : endPoint
  10775. );
  10776. if (siblings.length) {
  10777. callback(exclude(siblings));
  10778. }
  10779. // Walk right leaf
  10780. walkBoundary(endContainer, endPoint);
  10781. };
  10782. /**
  10783. * Splits the specified range at it's start/end points.
  10784. *
  10785. * @private
  10786. * @param {Range/RangeObject} rng Range to split.
  10787. * @return {Object} Range position object.
  10788. */
  10789. this.split = function(rng) {
  10790. var startContainer = rng.startContainer,
  10791. startOffset = rng.startOffset,
  10792. endContainer = rng.endContainer,
  10793. endOffset = rng.endOffset;
  10794. function splitText(node, offset) {
  10795. return node.splitText(offset);
  10796. }
  10797. // Handle single text node
  10798. if (startContainer == endContainer && startContainer.nodeType == 3) {
  10799. if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  10800. endContainer = splitText(startContainer, startOffset);
  10801. startContainer = endContainer.previousSibling;
  10802. if (endOffset > startOffset) {
  10803. endOffset = endOffset - startOffset;
  10804. startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
  10805. endOffset = endContainer.nodeValue.length;
  10806. startOffset = 0;
  10807. } else {
  10808. endOffset = 0;
  10809. }
  10810. }
  10811. } else {
  10812. // Split startContainer text node if needed
  10813. if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
  10814. startContainer = splitText(startContainer, startOffset);
  10815. startOffset = 0;
  10816. }
  10817. // Split endContainer text node if needed
  10818. if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
  10819. endContainer = splitText(endContainer, endOffset).previousSibling;
  10820. endOffset = endContainer.nodeValue.length;
  10821. }
  10822. }
  10823. return {
  10824. startContainer: startContainer,
  10825. startOffset: startOffset,
  10826. endContainer: endContainer,
  10827. endOffset: endOffset
  10828. };
  10829. };
  10830. /**
  10831. * Normalizes the specified range by finding the closest best suitable caret location.
  10832. *
  10833. * @private
  10834. * @param {Range} rng Range to normalize.
  10835. * @return {Boolean} True/false if the specified range was normalized or not.
  10836. */
  10837. this.normalize = function(rng) {
  10838. var normalized, collapsed;
  10839. function normalizeEndPoint(start) {
  10840. var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
  10841. var directionLeft, isAfterNode;
  10842. function hasBrBeforeAfter(node, left) {
  10843. var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
  10844. while ((node = walker[left ? 'prev' : 'next']())) {
  10845. if (node.nodeName === "BR") {
  10846. return true;
  10847. }
  10848. }
  10849. }
  10850. function isPrevNode(node, name) {
  10851. return node.previousSibling && node.previousSibling.nodeName == name;
  10852. }
  10853. // Walks the dom left/right to find a suitable text node to move the endpoint into
  10854. // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
  10855. function findTextNodeRelative(left, startNode) {
  10856. var walker, lastInlineElement, parentBlockContainer;
  10857. startNode = startNode || container;
  10858. parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
  10859. // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
  10860. // This: <p><br>|</p> becomes <p>|<br></p>
  10861. if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
  10862. container = startNode.parentNode;
  10863. offset = dom.nodeIndex(startNode);
  10864. normalized = true;
  10865. return;
  10866. }
  10867. // Walk left until we hit a text node we can move to or a block/br/img
  10868. walker = new TreeWalker(startNode, parentBlockContainer);
  10869. while ((node = walker[left ? 'prev' : 'next']())) {
  10870. // Found text node that has a length
  10871. if (node.nodeType === 3 && node.nodeValue.length > 0) {
  10872. container = node;
  10873. offset = left ? node.nodeValue.length : 0;
  10874. normalized = true;
  10875. return;
  10876. }
  10877. // Break if we find a block or a BR/IMG/INPUT etc
  10878. if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  10879. return;
  10880. }
  10881. lastInlineElement = node;
  10882. }
  10883. // Only fetch the last inline element when in caret mode for now
  10884. if (collapsed && lastInlineElement) {
  10885. container = lastInlineElement;
  10886. normalized = true;
  10887. offset = 0;
  10888. }
  10889. }
  10890. container = rng[(start ? 'start' : 'end') + 'Container'];
  10891. offset = rng[(start ? 'start' : 'end') + 'Offset'];
  10892. isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
  10893. nonEmptyElementsMap = dom.schema.getNonEmptyElements();
  10894. directionLeft = start;
  10895. if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
  10896. directionLeft = false;
  10897. }
  10898. // If the container is a document move it to the body element
  10899. if (container.nodeType === 9) {
  10900. container = dom.getRoot();
  10901. offset = 0;
  10902. }
  10903. // If the container is body try move it into the closest text node or position
  10904. if (container === body) {
  10905. // If start is before/after a image, table etc
  10906. if (directionLeft) {
  10907. node = container.childNodes[offset > 0 ? offset - 1 : 0];
  10908. if (node) {
  10909. nodeName = node.nodeName.toLowerCase();
  10910. if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
  10911. return;
  10912. }
  10913. }
  10914. }
  10915. // Resolve the index
  10916. if (container.hasChildNodes()) {
  10917. offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
  10918. container = container.childNodes[offset];
  10919. offset = 0;
  10920. // Don't walk into elements that doesn't have any child nodes like a IMG
  10921. if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
  10922. // Walk the DOM to find a text node to place the caret at or a BR
  10923. node = container;
  10924. walker = new TreeWalker(container, body);
  10925. do {
  10926. // Found a text node use that position
  10927. if (node.nodeType === 3 && node.nodeValue.length > 0) {
  10928. offset = directionLeft ? 0 : node.nodeValue.length;
  10929. container = node;
  10930. normalized = true;
  10931. break;
  10932. }
  10933. // Found a BR/IMG element that we can place the caret before
  10934. if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  10935. offset = dom.nodeIndex(node);
  10936. container = node.parentNode;
  10937. // Put caret after image when moving the end point
  10938. if (node.nodeName == "IMG" && !directionLeft) {
  10939. offset++;
  10940. }
  10941. normalized = true;
  10942. break;
  10943. }
  10944. } while ((node = (directionLeft ? walker.next() : walker.prev())));
  10945. }
  10946. }
  10947. }
  10948. // Lean the caret to the left if possible
  10949. if (collapsed) {
  10950. // So this: <b>x</b><i>|x</i>
  10951. // Becomes: <b>x|</b><i>x</i>
  10952. // Seems that only gecko has issues with this
  10953. if (container.nodeType === 3 && offset === 0) {
  10954. findTextNodeRelative(true);
  10955. }
  10956. // Lean left into empty inline elements when the caret is before a BR
  10957. // So this: <i><b></b><i>|<br></i>
  10958. // Becomes: <i><b>|</b><i><br></i>
  10959. // Seems that only gecko has issues with this.
  10960. // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
  10961. if (container.nodeType === 1) {
  10962. node = container.childNodes[offset];
  10963. // Offset is after the containers last child
  10964. // then use the previous child for normalization
  10965. if (!node) {
  10966. node = container.childNodes[offset - 1];
  10967. }
  10968. if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
  10969. !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
  10970. findTextNodeRelative(true, node);
  10971. }
  10972. }
  10973. }
  10974. // Lean the start of the selection right if possible
  10975. // So this: x[<b>x]</b>
  10976. // Becomes: x<b>[x]</b>
  10977. if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
  10978. findTextNodeRelative(false);
  10979. }
  10980. // Set endpoint if it was normalized
  10981. if (normalized) {
  10982. rng['set' + (start ? 'Start' : 'End')](container, offset);
  10983. }
  10984. }
  10985. collapsed = rng.collapsed;
  10986. normalizeEndPoint(true);
  10987. if (!collapsed) {
  10988. normalizeEndPoint();
  10989. }
  10990. // If it was collapsed then make sure it still is
  10991. if (normalized && collapsed) {
  10992. rng.collapse(true);
  10993. }
  10994. return normalized;
  10995. };
  10996. }
  10997. /**
  10998. * Compares two ranges and checks if they are equal.
  10999. *
  11000. * @static
  11001. * @method compareRanges
  11002. * @param {DOMRange} rng1 First range to compare.
  11003. * @param {DOMRange} rng2 First range to compare.
  11004. * @return {Boolean} true/false if the ranges are equal.
  11005. */
  11006. RangeUtils.compareRanges = function(rng1, rng2) {
  11007. if (rng1 && rng2) {
  11008. // Compare native IE ranges
  11009. if (rng1.item || rng1.duplicate) {
  11010. // Both are control ranges and the selected element matches
  11011. if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
  11012. return true;
  11013. }
  11014. // Both are text ranges and the range matches
  11015. if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
  11016. return true;
  11017. }
  11018. } else {
  11019. // Compare w3c ranges
  11020. return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
  11021. }
  11022. }
  11023. return false;
  11024. };
  11025. return RangeUtils;
  11026. });
  11027. // Included from: js/tinymce/classes/dom/Selection.js
  11028. /**
  11029. * Selection.js
  11030. *
  11031. * Copyright, Moxiecode Systems AB
  11032. * Released under LGPL License.
  11033. *
  11034. * License: http://www.tinymce.com/license
  11035. * Contributing: http://www.tinymce.com/contributing
  11036. */
  11037. /**
  11038. * This class handles text and control selection it's an crossbrowser utility class.
  11039. * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
  11040. *
  11041. * @class tinymce.dom.Selection
  11042. * @example
  11043. * // Getting the currently selected node for the active editor
  11044. * alert(tinymce.activeEditor.selection.getNode().nodeName);
  11045. */
  11046. define("tinymce/dom/Selection", [
  11047. "tinymce/dom/TreeWalker",
  11048. "tinymce/dom/TridentSelection",
  11049. "tinymce/dom/ControlSelection",
  11050. "tinymce/dom/RangeUtils",
  11051. "tinymce/Env",
  11052. "tinymce/util/Tools"
  11053. ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, Env, Tools) {
  11054. var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
  11055. var isIE = Env.ie, isOpera = Env.opera;
  11056. /**
  11057. * Constructs a new selection instance.
  11058. *
  11059. * @constructor
  11060. * @method Selection
  11061. * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
  11062. * @param {Window} win Window to bind the selection object to.
  11063. * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
  11064. */
  11065. function Selection(dom, win, serializer, editor) {
  11066. var self = this;
  11067. self.dom = dom;
  11068. self.win = win;
  11069. self.serializer = serializer;
  11070. self.editor = editor;
  11071. self.controlSelection = new ControlSelection(self, editor);
  11072. // No W3C Range support
  11073. if (!self.win.getSelection) {
  11074. self.tridentSel = new TridentSelection(self);
  11075. }
  11076. }
  11077. Selection.prototype = {
  11078. /**
  11079. * Move the selection cursor range to the specified node and offset.
  11080. * If there is no node specified it will move it to the first suitable location within the body.
  11081. *
  11082. * @method setCursorLocation
  11083. * @param {Node} node Optional node to put the cursor in.
  11084. * @param {Number} offset Optional offset from the start of the node to put the cursor at.
  11085. */
  11086. setCursorLocation: function(node, offset) {
  11087. var self = this, rng = self.dom.createRng();
  11088. if (!node) {
  11089. self._moveEndPoint(rng, self.editor.getBody(), true);
  11090. self.setRng(rng);
  11091. } else {
  11092. rng.setStart(node, offset);
  11093. rng.setEnd(node, offset);
  11094. self.setRng(rng);
  11095. self.collapse(false);
  11096. }
  11097. },
  11098. /**
  11099. * Returns the selected contents using the DOM serializer passed in to this class.
  11100. *
  11101. * @method getContent
  11102. * @param {Object} s Optional settings class with for example output format text or html.
  11103. * @return {String} Selected contents in for example HTML format.
  11104. * @example
  11105. * // Alerts the currently selected contents
  11106. * alert(tinymce.activeEditor.selection.getContent());
  11107. *
  11108. * // Alerts the currently selected contents as plain text
  11109. * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
  11110. */
  11111. getContent: function(args) {
  11112. var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
  11113. var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
  11114. args = args || {};
  11115. whiteSpaceBefore = whiteSpaceAfter = '';
  11116. args.get = true;
  11117. args.format = args.format || 'html';
  11118. args.selection = true;
  11119. self.editor.fire('BeforeGetContent', args);
  11120. if (args.format == 'text') {
  11121. return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
  11122. }
  11123. if (rng.cloneContents) {
  11124. fragment = rng.cloneContents();
  11125. if (fragment) {
  11126. tmpElm.appendChild(fragment);
  11127. }
  11128. } else if (rng.item !== undefined || rng.htmlText !== undefined) {
  11129. // IE will produce invalid markup if elements are present that
  11130. // it doesn't understand like custom elements or HTML5 elements.
  11131. // Adding a BR in front of the contents and then remoiving it seems to fix it though.
  11132. tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
  11133. tmpElm.removeChild(tmpElm.firstChild);
  11134. } else {
  11135. tmpElm.innerHTML = rng.toString();
  11136. }
  11137. // Keep whitespace before and after
  11138. if (/^\s/.test(tmpElm.innerHTML)) {
  11139. whiteSpaceBefore = ' ';
  11140. }
  11141. if (/\s+$/.test(tmpElm.innerHTML)) {
  11142. whiteSpaceAfter = ' ';
  11143. }
  11144. args.getInner = true;
  11145. args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
  11146. self.editor.fire('GetContent', args);
  11147. return args.content;
  11148. },
  11149. /**
  11150. * Sets the current selection to the specified content. If any contents is selected it will be replaced
  11151. * with the contents passed in to this function. If there is no selection the contents will be inserted
  11152. * where the caret is placed in the editor/page.
  11153. *
  11154. * @method setContent
  11155. * @param {String} content HTML contents to set could also be other formats depending on settings.
  11156. * @param {Object} args Optional settings object with for example data format.
  11157. * @example
  11158. * // Inserts some HTML contents at the current selection
  11159. * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
  11160. */
  11161. setContent: function(content, args) {
  11162. var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
  11163. args = args || {format: 'html'};
  11164. args.set = true;
  11165. args.selection = true;
  11166. content = args.content = content;
  11167. // Dispatch before set content event
  11168. if (!args.no_events) {
  11169. self.editor.fire('BeforeSetContent', args);
  11170. }
  11171. content = args.content;
  11172. if (rng.insertNode) {
  11173. // Make caret marker since insertNode places the caret in the beginning of text after insert
  11174. content += '<span id="__caret">_</span>';
  11175. // Delete and insert new node
  11176. if (rng.startContainer == doc && rng.endContainer == doc) {
  11177. // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
  11178. doc.body.innerHTML = content;
  11179. } else {
  11180. rng.deleteContents();
  11181. if (doc.body.childNodes.length === 0) {
  11182. doc.body.innerHTML = content;
  11183. } else {
  11184. // createContextualFragment doesn't exists in IE 9 DOMRanges
  11185. if (rng.createContextualFragment) {
  11186. rng.insertNode(rng.createContextualFragment(content));
  11187. } else {
  11188. // Fake createContextualFragment call in IE 9
  11189. frag = doc.createDocumentFragment();
  11190. temp = doc.createElement('div');
  11191. frag.appendChild(temp);
  11192. temp.outerHTML = content;
  11193. rng.insertNode(frag);
  11194. }
  11195. }
  11196. }
  11197. // Move to caret marker
  11198. caretNode = self.dom.get('__caret');
  11199. // Make sure we wrap it compleatly, Opera fails with a simple select call
  11200. rng = doc.createRange();
  11201. rng.setStartBefore(caretNode);
  11202. rng.setEndBefore(caretNode);
  11203. self.setRng(rng);
  11204. // Remove the caret position
  11205. self.dom.remove('__caret');
  11206. try {
  11207. self.setRng(rng);
  11208. } catch (ex) {
  11209. // Might fail on Opera for some odd reason
  11210. }
  11211. } else {
  11212. if (rng.item) {
  11213. // Delete content and get caret text selection
  11214. doc.execCommand('Delete', false, null);
  11215. rng = self.getRng();
  11216. }
  11217. // Explorer removes spaces from the beginning of pasted contents
  11218. if (/^\s+/.test(content)) {
  11219. rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
  11220. self.dom.remove('__mce_tmp');
  11221. } else {
  11222. rng.pasteHTML(content);
  11223. }
  11224. }
  11225. // Dispatch set content event
  11226. if (!args.no_events) {
  11227. self.editor.fire('SetContent', args);
  11228. }
  11229. },
  11230. /**
  11231. * Returns the start element of a selection range. If the start is in a text
  11232. * node the parent element will be returned.
  11233. *
  11234. * @method getStart
  11235. * @return {Element} Start element of selection range.
  11236. */
  11237. getStart: function() {
  11238. var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
  11239. if (rng.duplicate || rng.item) {
  11240. // Control selection, return first item
  11241. if (rng.item) {
  11242. return rng.item(0);
  11243. }
  11244. // Get start element
  11245. checkRng = rng.duplicate();
  11246. checkRng.collapse(1);
  11247. startElement = checkRng.parentElement();
  11248. if (startElement.ownerDocument !== self.dom.doc) {
  11249. startElement = self.dom.getRoot();
  11250. }
  11251. // Check if range parent is inside the start element, then return the inner parent element
  11252. // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
  11253. parentElement = node = rng.parentElement();
  11254. while ((node = node.parentNode)) {
  11255. if (node == startElement) {
  11256. startElement = parentElement;
  11257. break;
  11258. }
  11259. }
  11260. return startElement;
  11261. } else {
  11262. startElement = rng.startContainer;
  11263. if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
  11264. startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
  11265. }
  11266. if (startElement && startElement.nodeType == 3) {
  11267. return startElement.parentNode;
  11268. }
  11269. return startElement;
  11270. }
  11271. },
  11272. /**
  11273. * Returns the end element of a selection range. If the end is in a text
  11274. * node the parent element will be returned.
  11275. *
  11276. * @method getEnd
  11277. * @return {Element} End element of selection range.
  11278. */
  11279. getEnd: function() {
  11280. var self = this, rng = self.getRng(), endElement, endOffset;
  11281. if (rng.duplicate || rng.item) {
  11282. if (rng.item) {
  11283. return rng.item(0);
  11284. }
  11285. rng = rng.duplicate();
  11286. rng.collapse(0);
  11287. endElement = rng.parentElement();
  11288. if (endElement.ownerDocument !== self.dom.doc) {
  11289. endElement = self.dom.getRoot();
  11290. }
  11291. if (endElement && endElement.nodeName == 'BODY') {
  11292. return endElement.lastChild || endElement;
  11293. }
  11294. return endElement;
  11295. } else {
  11296. endElement = rng.endContainer;
  11297. endOffset = rng.endOffset;
  11298. if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
  11299. endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
  11300. }
  11301. if (endElement && endElement.nodeType == 3) {
  11302. return endElement.parentNode;
  11303. }
  11304. return endElement;
  11305. }
  11306. },
  11307. /**
  11308. * Returns a bookmark location for the current selection. This bookmark object
  11309. * can then be used to restore the selection after some content modification to the document.
  11310. *
  11311. * @method getBookmark
  11312. * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
  11313. * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
  11314. * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
  11315. * @example
  11316. * // Stores a bookmark of the current selection
  11317. * var bm = tinymce.activeEditor.selection.getBookmark();
  11318. *
  11319. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  11320. *
  11321. * // Restore the selection bookmark
  11322. * tinymce.activeEditor.selection.moveToBookmark(bm);
  11323. */
  11324. getBookmark: function(type, normalized) {
  11325. var self = this, dom = self.dom, rng, rng2, id, collapsed, name, element, chr = '&#xFEFF;', styles;
  11326. function findIndex(name, element) {
  11327. var index = 0;
  11328. each(dom.select(name), function(node, i) {
  11329. if (node == element) {
  11330. index = i;
  11331. }
  11332. });
  11333. return index;
  11334. }
  11335. function normalizeTableCellSelection(rng) {
  11336. function moveEndPoint(start) {
  11337. var container, offset, childNodes, prefix = start ? 'start' : 'end';
  11338. container = rng[prefix + 'Container'];
  11339. offset = rng[prefix + 'Offset'];
  11340. if (container.nodeType == 1 && container.nodeName == "TR") {
  11341. childNodes = container.childNodes;
  11342. container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
  11343. if (container) {
  11344. offset = start ? 0 : container.childNodes.length;
  11345. rng['set' + (start ? 'Start' : 'End')](container, offset);
  11346. }
  11347. }
  11348. }
  11349. moveEndPoint(true);
  11350. moveEndPoint();
  11351. return rng;
  11352. }
  11353. function getLocation() {
  11354. var rng = self.getRng(true), root = dom.getRoot(), bookmark = {};
  11355. function getPoint(rng, start) {
  11356. var container = rng[start ? 'startContainer' : 'endContainer'],
  11357. offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
  11358. if (container.nodeType == 3) {
  11359. if (normalized) {
  11360. for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
  11361. offset += node.nodeValue.length;
  11362. }
  11363. }
  11364. point.push(offset);
  11365. } else {
  11366. childNodes = container.childNodes;
  11367. if (offset >= childNodes.length && childNodes.length) {
  11368. after = 1;
  11369. offset = Math.max(0, childNodes.length - 1);
  11370. }
  11371. point.push(self.dom.nodeIndex(childNodes[offset], normalized) + after);
  11372. }
  11373. for (; container && container != root; container = container.parentNode) {
  11374. point.push(self.dom.nodeIndex(container, normalized));
  11375. }
  11376. return point;
  11377. }
  11378. bookmark.start = getPoint(rng, true);
  11379. if (!self.isCollapsed()) {
  11380. bookmark.end = getPoint(rng);
  11381. }
  11382. return bookmark;
  11383. }
  11384. if (type == 2) {
  11385. element = self.getNode();
  11386. name = element ? element.nodeName : null;
  11387. if (name == 'IMG') {
  11388. return {name: name, index: findIndex(name, element)};
  11389. }
  11390. if (self.tridentSel) {
  11391. return self.tridentSel.getBookmark(type);
  11392. }
  11393. return getLocation();
  11394. }
  11395. // Handle simple range
  11396. if (type) {
  11397. return {rng: self.getRng()};
  11398. }
  11399. rng = self.getRng();
  11400. id = dom.uniqueId();
  11401. collapsed = self.isCollapsed();
  11402. styles = 'overflow:hidden;line-height:0px';
  11403. // Explorer method
  11404. if (rng.duplicate || rng.item) {
  11405. // Text selection
  11406. if (!rng.item) {
  11407. rng2 = rng.duplicate();
  11408. try {
  11409. // Insert start marker
  11410. rng.collapse();
  11411. rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
  11412. // Insert end marker
  11413. if (!collapsed) {
  11414. rng2.collapse(false);
  11415. // Detect the empty space after block elements in IE and move the
  11416. // end back one character <p></p>] becomes <p>]</p>
  11417. rng.moveToElementText(rng2.parentElement());
  11418. if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
  11419. rng2.move('character', -1);
  11420. }
  11421. rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
  11422. }
  11423. } catch (ex) {
  11424. // IE might throw unspecified error so lets ignore it
  11425. return null;
  11426. }
  11427. } else {
  11428. // Control selection
  11429. element = rng.item(0);
  11430. name = element.nodeName;
  11431. return {name: name, index: findIndex(name, element)};
  11432. }
  11433. } else {
  11434. element = self.getNode();
  11435. name = element.nodeName;
  11436. if (name == 'IMG') {
  11437. return {name: name, index: findIndex(name, element)};
  11438. }
  11439. // W3C method
  11440. rng2 = normalizeTableCellSelection(rng.cloneRange());
  11441. // Insert end marker
  11442. if (!collapsed) {
  11443. rng2.collapse(false);
  11444. rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
  11445. }
  11446. rng = normalizeTableCellSelection(rng);
  11447. rng.collapse(true);
  11448. rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
  11449. }
  11450. self.moveToBookmark({id: id, keep: 1});
  11451. return {id: id};
  11452. },
  11453. /**
  11454. * Restores the selection to the specified bookmark.
  11455. *
  11456. * @method moveToBookmark
  11457. * @param {Object} bookmark Bookmark to restore selection from.
  11458. * @return {Boolean} true/false if it was successful or not.
  11459. * @example
  11460. * // Stores a bookmark of the current selection
  11461. * var bm = tinymce.activeEditor.selection.getBookmark();
  11462. *
  11463. * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
  11464. *
  11465. * // Restore the selection bookmark
  11466. * tinymce.activeEditor.selection.moveToBookmark(bm);
  11467. */
  11468. moveToBookmark: function(bookmark) {
  11469. var self = this, dom = self.dom, rng, root, startContainer, endContainer, startOffset, endOffset;
  11470. function setEndPoint(start) {
  11471. var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
  11472. if (point) {
  11473. offset = point[0];
  11474. // Find container node
  11475. for (node = root, i = point.length - 1; i >= 1; i--) {
  11476. children = node.childNodes;
  11477. if (point[i] > children.length - 1) {
  11478. return;
  11479. }
  11480. node = children[point[i]];
  11481. }
  11482. // Move text offset to best suitable location
  11483. if (node.nodeType === 3) {
  11484. offset = Math.min(point[0], node.nodeValue.length);
  11485. }
  11486. // Move element offset to best suitable location
  11487. if (node.nodeType === 1) {
  11488. offset = Math.min(point[0], node.childNodes.length);
  11489. }
  11490. // Set offset within container node
  11491. if (start) {
  11492. rng.setStart(node, offset);
  11493. } else {
  11494. rng.setEnd(node, offset);
  11495. }
  11496. }
  11497. return true;
  11498. }
  11499. function restoreEndPoint(suffix) {
  11500. var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
  11501. if (marker) {
  11502. node = marker.parentNode;
  11503. if (suffix == 'start') {
  11504. if (!keep) {
  11505. idx = dom.nodeIndex(marker);
  11506. } else {
  11507. node = marker.firstChild;
  11508. idx = 1;
  11509. }
  11510. startContainer = endContainer = node;
  11511. startOffset = endOffset = idx;
  11512. } else {
  11513. if (!keep) {
  11514. idx = dom.nodeIndex(marker);
  11515. } else {
  11516. node = marker.firstChild;
  11517. idx = 1;
  11518. }
  11519. endContainer = node;
  11520. endOffset = idx;
  11521. }
  11522. if (!keep) {
  11523. prev = marker.previousSibling;
  11524. next = marker.nextSibling;
  11525. // Remove all marker text nodes
  11526. each(grep(marker.childNodes), function(node) {
  11527. if (node.nodeType == 3) {
  11528. node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
  11529. }
  11530. });
  11531. // Remove marker but keep children if for example contents where inserted into the marker
  11532. // Also remove duplicated instances of the marker for example by a
  11533. // split operation or by WebKit auto split on paste feature
  11534. while ((marker = dom.get(bookmark.id + '_' + suffix))) {
  11535. dom.remove(marker, 1);
  11536. }
  11537. // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
  11538. // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
  11539. // isn't worth the effort. Sorry, Opera but it's just a fact
  11540. if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !isOpera) {
  11541. idx = prev.nodeValue.length;
  11542. prev.appendData(next.nodeValue);
  11543. dom.remove(next);
  11544. if (suffix == 'start') {
  11545. startContainer = endContainer = prev;
  11546. startOffset = endOffset = idx;
  11547. } else {
  11548. endContainer = prev;
  11549. endOffset = idx;
  11550. }
  11551. }
  11552. }
  11553. }
  11554. }
  11555. function addBogus(node) {
  11556. // Adds a bogus BR element for empty block elements
  11557. if (dom.isBlock(node) && !node.innerHTML && !isIE) {
  11558. node.innerHTML = '<br data-mce-bogus="1" />';
  11559. }
  11560. return node;
  11561. }
  11562. if (bookmark) {
  11563. if (bookmark.start) {
  11564. rng = dom.createRng();
  11565. root = dom.getRoot();
  11566. if (self.tridentSel) {
  11567. return self.tridentSel.moveToBookmark(bookmark);
  11568. }
  11569. if (setEndPoint(true) && setEndPoint()) {
  11570. self.setRng(rng);
  11571. }
  11572. } else if (bookmark.id) {
  11573. // Restore start/end points
  11574. restoreEndPoint('start');
  11575. restoreEndPoint('end');
  11576. if (startContainer) {
  11577. rng = dom.createRng();
  11578. rng.setStart(addBogus(startContainer), startOffset);
  11579. rng.setEnd(addBogus(endContainer), endOffset);
  11580. self.setRng(rng);
  11581. }
  11582. } else if (bookmark.name) {
  11583. self.select(dom.select(bookmark.name)[bookmark.index]);
  11584. } else if (bookmark.rng) {
  11585. self.setRng(bookmark.rng);
  11586. }
  11587. }
  11588. },
  11589. /**
  11590. * Selects the specified element. This will place the start and end of the selection range around the element.
  11591. *
  11592. * @method select
  11593. * @param {Element} node HMTL DOM element to select.
  11594. * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
  11595. * @return {Element} Selected element the same element as the one that got passed in.
  11596. * @example
  11597. * // Select the first paragraph in the active editor
  11598. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  11599. */
  11600. select: function(node, content) {
  11601. var self = this, dom = self.dom, rng = dom.createRng(), idx;
  11602. // Clear stored range set by FocusManager
  11603. self.lastFocusBookmark = null;
  11604. if (node) {
  11605. if (!content && self.controlSelection.controlSelect(node)) {
  11606. return;
  11607. }
  11608. idx = dom.nodeIndex(node);
  11609. rng.setStart(node.parentNode, idx);
  11610. rng.setEnd(node.parentNode, idx + 1);
  11611. // Find first/last text node or BR element
  11612. if (content) {
  11613. self._moveEndPoint(rng, node, true);
  11614. self._moveEndPoint(rng, node);
  11615. }
  11616. self.setRng(rng);
  11617. }
  11618. return node;
  11619. },
  11620. /**
  11621. * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
  11622. *
  11623. * @method isCollapsed
  11624. * @return {Boolean} true/false state if the selection range is collapsed or not.
  11625. * Collapsed means if it's a caret or a larger selection.
  11626. */
  11627. isCollapsed: function() {
  11628. var self = this, rng = self.getRng(), sel = self.getSel();
  11629. if (!rng || rng.item) {
  11630. return false;
  11631. }
  11632. if (rng.compareEndPoints) {
  11633. return rng.compareEndPoints('StartToEnd', rng) === 0;
  11634. }
  11635. return !sel || rng.collapsed;
  11636. },
  11637. /**
  11638. * Collapse the selection to start or end of range.
  11639. *
  11640. * @method collapse
  11641. * @param {Boolean} to_start Optional boolean state if to collapse to end or not. Defaults to start.
  11642. */
  11643. collapse: function(to_start) {
  11644. var self = this, rng = self.getRng(), node;
  11645. // Control range on IE
  11646. if (rng.item) {
  11647. node = rng.item(0);
  11648. rng = self.win.document.body.createTextRange();
  11649. rng.moveToElementText(node);
  11650. }
  11651. rng.collapse(!!to_start);
  11652. self.setRng(rng);
  11653. },
  11654. /**
  11655. * Returns the browsers internal selection object.
  11656. *
  11657. * @method getSel
  11658. * @return {Selection} Internal browser selection object.
  11659. */
  11660. getSel: function() {
  11661. var win = this.win;
  11662. return win.getSelection ? win.getSelection() : win.document.selection;
  11663. },
  11664. /**
  11665. * Returns the browsers internal range object.
  11666. *
  11667. * @method getRng
  11668. * @param {Boolean} w3c Forces a compatible W3C range on IE.
  11669. * @return {Range} Internal browser range object.
  11670. * @see http://www.quirksmode.org/dom/range_intro.html
  11671. * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
  11672. */
  11673. getRng: function(w3c) {
  11674. var self = this, selection, rng, elm, doc = self.win.document, ieRng;
  11675. function tryCompareBounderyPoints(how, sourceRange, destinationRange) {
  11676. try {
  11677. return sourceRange.compareBoundaryPoints(how, destinationRange);
  11678. } catch (ex) {
  11679. // Gecko throws wrong document exception if the range points
  11680. // to nodes that where removed from the dom #6690
  11681. // Browsers should mutate existing DOMRange instances so that they always point
  11682. // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
  11683. // For performance reasons just return -1
  11684. return -1;
  11685. }
  11686. }
  11687. // Use last rng passed from FocusManager if it's available this enables
  11688. // calls to editor.selection.getStart() to work when caret focus is lost on IE
  11689. if (!w3c && self.lastFocusBookmark) {
  11690. var bookmark = self.lastFocusBookmark;
  11691. // Convert bookmark to range IE 11 fix
  11692. if (bookmark.startContainer) {
  11693. rng = doc.createRange();
  11694. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  11695. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  11696. } else {
  11697. rng = bookmark;
  11698. }
  11699. return rng;
  11700. }
  11701. // Found tridentSel object then we need to use that one
  11702. if (w3c && self.tridentSel) {
  11703. return self.tridentSel.getRangeAt(0);
  11704. }
  11705. try {
  11706. if ((selection = self.getSel())) {
  11707. if (selection.rangeCount > 0) {
  11708. rng = selection.getRangeAt(0);
  11709. } else {
  11710. rng = selection.createRange ? selection.createRange() : doc.createRange();
  11711. }
  11712. }
  11713. } catch (ex) {
  11714. // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
  11715. }
  11716. // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
  11717. // IE 11 doesn't support the selection object so we check for that as well
  11718. if (isIE && rng && rng.setStart && doc.selection) {
  11719. try {
  11720. // IE will sometimes throw an exception here
  11721. ieRng = doc.selection.createRange();
  11722. } catch (ex) {
  11723. }
  11724. if (ieRng && ieRng.item) {
  11725. elm = ieRng.item(0);
  11726. rng = doc.createRange();
  11727. rng.setStartBefore(elm);
  11728. rng.setEndAfter(elm);
  11729. }
  11730. }
  11731. // No range found then create an empty one
  11732. // This can occur when the editor is placed in a hidden container element on Gecko
  11733. // Or on IE when there was an exception
  11734. if (!rng) {
  11735. rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
  11736. }
  11737. // If range is at start of document then move it to start of body
  11738. if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
  11739. elm = self.dom.getRoot();
  11740. rng.setStart(elm, 0);
  11741. rng.setEnd(elm, 0);
  11742. }
  11743. if (self.selectedRange && self.explicitRange) {
  11744. if (tryCompareBounderyPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
  11745. tryCompareBounderyPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
  11746. // Safari, Opera and Chrome only ever select text which causes the range to change.
  11747. // This lets us use the originally set range if the selection hasn't been changed by the user.
  11748. rng = self.explicitRange;
  11749. } else {
  11750. self.selectedRange = null;
  11751. self.explicitRange = null;
  11752. }
  11753. }
  11754. return rng;
  11755. },
  11756. /**
  11757. * Changes the selection to the specified DOM range.
  11758. *
  11759. * @method setRng
  11760. * @param {Range} rng Range to select.
  11761. */
  11762. setRng: function(rng, forward) {
  11763. var self = this, sel;
  11764. // Is IE specific range
  11765. if (rng.select) {
  11766. try {
  11767. rng.select();
  11768. } catch (ex) {
  11769. // Needed for some odd IE bug #1843306
  11770. }
  11771. return;
  11772. }
  11773. if (!self.tridentSel) {
  11774. sel = self.getSel();
  11775. if (sel) {
  11776. self.explicitRange = rng;
  11777. try {
  11778. sel.removeAllRanges();
  11779. sel.addRange(rng);
  11780. } catch (ex) {
  11781. // IE might throw errors here if the editor is within a hidden container and selection is changed
  11782. }
  11783. // Forward is set to false and we have an extend function
  11784. if (forward === false && sel.extend) {
  11785. sel.collapse(rng.endContainer, rng.endOffset);
  11786. sel.extend(rng.startContainer, rng.startOffset);
  11787. }
  11788. // adding range isn't always successful so we need to check range count otherwise an exception can occur
  11789. self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
  11790. }
  11791. } else {
  11792. // Is W3C Range fake range on IE
  11793. if (rng.cloneRange) {
  11794. try {
  11795. self.tridentSel.addRange(rng);
  11796. return;
  11797. } catch (ex) {
  11798. //IE9 throws an error here if called before selection is placed in the editor
  11799. }
  11800. }
  11801. }
  11802. },
  11803. /**
  11804. * Sets the current selection to the specified DOM element.
  11805. *
  11806. * @method setNode
  11807. * @param {Element} elm Element to set as the contents of the selection.
  11808. * @return {Element} Returns the element that got passed in.
  11809. * @example
  11810. * // Inserts a DOM node at current selection/caret location
  11811. * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
  11812. */
  11813. setNode: function(elm) {
  11814. var self = this;
  11815. self.setContent(self.dom.getOuterHTML(elm));
  11816. return elm;
  11817. },
  11818. /**
  11819. * Returns the currently selected element or the common ancestor element for both start and end of the selection.
  11820. *
  11821. * @method getNode
  11822. * @return {Element} Currently selected element or common ancestor element.
  11823. * @example
  11824. * // Alerts the currently selected elements node name
  11825. * alert(tinymce.activeEditor.selection.getNode().nodeName);
  11826. */
  11827. getNode: function() {
  11828. var self = this, rng = self.getRng(), elm;
  11829. var startContainer = rng.startContainer, endContainer = rng.endContainer;
  11830. var startOffset = rng.startOffset, endOffset = rng.endOffset, root = self.dom.getRoot();
  11831. function skipEmptyTextNodes(node, forwards) {
  11832. var orig = node;
  11833. while (node && node.nodeType === 3 && node.length === 0) {
  11834. node = forwards ? node.nextSibling : node.previousSibling;
  11835. }
  11836. return node || orig;
  11837. }
  11838. // Range maybe lost after the editor is made visible again
  11839. if (!rng) {
  11840. return root;
  11841. }
  11842. if (rng.setStart) {
  11843. elm = rng.commonAncestorContainer;
  11844. // Handle selection a image or other control like element such as anchors
  11845. if (!rng.collapsed) {
  11846. if (startContainer == endContainer) {
  11847. if (endOffset - startOffset < 2) {
  11848. if (startContainer.hasChildNodes()) {
  11849. elm = startContainer.childNodes[startOffset];
  11850. }
  11851. }
  11852. }
  11853. // If the anchor node is a element instead of a text node then return this element
  11854. //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
  11855. // return sel.anchorNode.childNodes[sel.anchorOffset];
  11856. // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
  11857. // This happens when you double click an underlined word in FireFox.
  11858. if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
  11859. if (startContainer.length === startOffset) {
  11860. startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
  11861. } else {
  11862. startContainer = startContainer.parentNode;
  11863. }
  11864. if (endOffset === 0) {
  11865. endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
  11866. } else {
  11867. endContainer = endContainer.parentNode;
  11868. }
  11869. if (startContainer && startContainer === endContainer) {
  11870. return startContainer;
  11871. }
  11872. }
  11873. }
  11874. if (elm && elm.nodeType == 3) {
  11875. return elm.parentNode;
  11876. }
  11877. return elm;
  11878. }
  11879. elm = rng.item ? rng.item(0) : rng.parentElement();
  11880. // IE 7 might return elements outside the iframe
  11881. if (elm.ownerDocument !== self.win.document) {
  11882. elm = root;
  11883. }
  11884. return elm;
  11885. },
  11886. getSelectedBlocks: function(startElm, endElm) {
  11887. var self = this, dom = self.dom, node, root, selectedBlocks = [];
  11888. root = dom.getRoot();
  11889. startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
  11890. endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
  11891. if (startElm && startElm != root) {
  11892. selectedBlocks.push(startElm);
  11893. }
  11894. if (startElm && endElm && startElm != endElm) {
  11895. node = startElm;
  11896. var walker = new TreeWalker(startElm, root);
  11897. while ((node = walker.next()) && node != endElm) {
  11898. if (dom.isBlock(node)) {
  11899. selectedBlocks.push(node);
  11900. }
  11901. }
  11902. }
  11903. if (endElm && startElm != endElm && endElm != root) {
  11904. selectedBlocks.push(endElm);
  11905. }
  11906. return selectedBlocks;
  11907. },
  11908. isForward: function() {
  11909. var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
  11910. // No support for selection direction then always return true
  11911. if (!sel || !sel.anchorNode || !sel.focusNode) {
  11912. return true;
  11913. }
  11914. anchorRange = dom.createRng();
  11915. anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
  11916. anchorRange.collapse(true);
  11917. focusRange = dom.createRng();
  11918. focusRange.setStart(sel.focusNode, sel.focusOffset);
  11919. focusRange.collapse(true);
  11920. return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
  11921. },
  11922. normalize: function() {
  11923. var self = this, rng = self.getRng();
  11924. if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
  11925. self.setRng(rng, self.isForward());
  11926. }
  11927. return rng;
  11928. },
  11929. /**
  11930. * Executes callback of the current selection matches the specified selector or not and passes the state and args to the callback.
  11931. *
  11932. * @method selectorChanged
  11933. * @param {String} selector CSS selector to check for.
  11934. * @param {function} callback Callback with state and args when the selector is matches or not.
  11935. */
  11936. selectorChanged: function(selector, callback) {
  11937. var self = this, currentSelectors;
  11938. if (!self.selectorChangedData) {
  11939. self.selectorChangedData = {};
  11940. currentSelectors = {};
  11941. self.editor.on('NodeChange', function(e) {
  11942. var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
  11943. // Check for new matching selectors
  11944. each(self.selectorChangedData, function(callbacks, selector) {
  11945. each(parents, function(node) {
  11946. if (dom.is(node, selector)) {
  11947. if (!currentSelectors[selector]) {
  11948. // Execute callbacks
  11949. each(callbacks, function(callback) {
  11950. callback(true, {node: node, selector: selector, parents: parents});
  11951. });
  11952. currentSelectors[selector] = callbacks;
  11953. }
  11954. matchedSelectors[selector] = callbacks;
  11955. return false;
  11956. }
  11957. });
  11958. });
  11959. // Check if current selectors still match
  11960. each(currentSelectors, function(callbacks, selector) {
  11961. if (!matchedSelectors[selector]) {
  11962. delete currentSelectors[selector];
  11963. each(callbacks, function(callback) {
  11964. callback(false, {node: node, selector: selector, parents: parents});
  11965. });
  11966. }
  11967. });
  11968. });
  11969. }
  11970. // Add selector listeners
  11971. if (!self.selectorChangedData[selector]) {
  11972. self.selectorChangedData[selector] = [];
  11973. }
  11974. self.selectorChangedData[selector].push(callback);
  11975. return self;
  11976. },
  11977. getScrollContainer: function() {
  11978. var scrollContainer, node = this.dom.getRoot();
  11979. while (node && node.nodeName != 'BODY') {
  11980. if (node.scrollHeight > node.clientHeight) {
  11981. scrollContainer = node;
  11982. break;
  11983. }
  11984. node = node.parentNode;
  11985. }
  11986. return scrollContainer;
  11987. },
  11988. scrollIntoView: function(elm) {
  11989. var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH;
  11990. function getPos(elm) {
  11991. var x = 0, y = 0;
  11992. var offsetParent = elm;
  11993. while (offsetParent && offsetParent.nodeType) {
  11994. x += offsetParent.offsetLeft || 0;
  11995. y += offsetParent.offsetTop || 0;
  11996. offsetParent = offsetParent.offsetParent;
  11997. }
  11998. return {x: x, y: y};
  11999. }
  12000. if (root.nodeName != 'BODY') {
  12001. var scrollContainer = self.getScrollContainer();
  12002. if (scrollContainer) {
  12003. y = getPos(elm).y - getPos(scrollContainer).y;
  12004. viewPortH = scrollContainer.clientHeight;
  12005. viewPortY = scrollContainer.scrollTop;
  12006. if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
  12007. scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
  12008. }
  12009. return;
  12010. }
  12011. }
  12012. viewPort = dom.getViewPort(self.editor.getWin());
  12013. y = dom.getPos(elm).y;
  12014. viewPortY = viewPort.y;
  12015. viewPortH = viewPort.h;
  12016. if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
  12017. self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
  12018. }
  12019. },
  12020. _moveEndPoint: function(rng, node, start) {
  12021. var root = node, walker = new TreeWalker(node, root);
  12022. var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
  12023. do {
  12024. // Text node
  12025. if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
  12026. if (start) {
  12027. rng.setStart(node, 0);
  12028. } else {
  12029. rng.setEnd(node, node.nodeValue.length);
  12030. }
  12031. return;
  12032. }
  12033. // BR/IMG/INPUT elements
  12034. if (nonEmptyElementsMap[node.nodeName]) {
  12035. if (start) {
  12036. rng.setStartBefore(node);
  12037. } else {
  12038. if (node.nodeName == 'BR') {
  12039. rng.setEndBefore(node);
  12040. } else {
  12041. rng.setEndAfter(node);
  12042. }
  12043. }
  12044. return;
  12045. }
  12046. // Found empty text block old IE can place the selection inside those
  12047. if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
  12048. if (start) {
  12049. rng.setStart(node, 0);
  12050. } else {
  12051. rng.setEnd(node, 0);
  12052. }
  12053. return;
  12054. }
  12055. } while ((node = (start ? walker.next() : walker.prev())));
  12056. // Failed to find any text node or other suitable location then move to the root of body
  12057. if (root.nodeName == 'BODY') {
  12058. if (start) {
  12059. rng.setStart(root, 0);
  12060. } else {
  12061. rng.setEnd(root, root.childNodes.length);
  12062. }
  12063. }
  12064. },
  12065. destroy: function() {
  12066. this.win = null;
  12067. this.controlSelection.destroy();
  12068. }
  12069. };
  12070. return Selection;
  12071. });
  12072. // Included from: js/tinymce/classes/fmt/Preview.js
  12073. /**
  12074. * Preview.js
  12075. *
  12076. * Copyright, Moxiecode Systems AB
  12077. * Released under LGPL License.
  12078. *
  12079. * License: http://www.tinymce.com/license
  12080. * Contributing: http://www.tinymce.com/contributing
  12081. */
  12082. /**
  12083. * Internal class for generating previews styles for formats.
  12084. *
  12085. * Example:
  12086. * Preview.getCssText(editor, 'bold');
  12087. *
  12088. * @class tinymce.fmt.Preview
  12089. * @private
  12090. */
  12091. define("tinymce/fmt/Preview", [
  12092. "tinymce/util/Tools"
  12093. ], function(Tools) {
  12094. var each = Tools.each;
  12095. function getCssText(editor, format) {
  12096. var name, previewElm, dom = editor.dom;
  12097. var previewCss = '', parentFontSize, previewStyles;
  12098. previewStyles = editor.settings.preview_styles;
  12099. // No preview forced
  12100. if (previewStyles === false) {
  12101. return '';
  12102. }
  12103. // Default preview
  12104. if (!previewStyles) {
  12105. previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
  12106. 'text-transform color background-color border border-radius outline text-shadow';
  12107. }
  12108. // Removes any variables since these can't be previewed
  12109. function removeVars(val) {
  12110. return val.replace(/%(\w+)/g, '');
  12111. }
  12112. // Create block/inline element to use for preview
  12113. if (typeof(format) == "string") {
  12114. format = editor.formatter.get(format);
  12115. if (!format) {
  12116. return;
  12117. }
  12118. format = format[0];
  12119. }
  12120. name = format.block || format.inline || 'span';
  12121. previewElm = dom.create(name);
  12122. // Add format styles to preview element
  12123. each(format.styles, function(value, name) {
  12124. value = removeVars(value);
  12125. if (value) {
  12126. dom.setStyle(previewElm, name, value);
  12127. }
  12128. });
  12129. // Add attributes to preview element
  12130. each(format.attributes, function(value, name) {
  12131. value = removeVars(value);
  12132. if (value) {
  12133. dom.setAttrib(previewElm, name, value);
  12134. }
  12135. });
  12136. // Add classes to preview element
  12137. each(format.classes, function(value) {
  12138. value = removeVars(value);
  12139. if (!dom.hasClass(previewElm, value)) {
  12140. dom.addClass(previewElm, value);
  12141. }
  12142. });
  12143. editor.fire('PreviewFormats');
  12144. // Add the previewElm outside the visual area
  12145. dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
  12146. editor.getBody().appendChild(previewElm);
  12147. // Get parent container font size so we can compute px values out of em/% for older IE:s
  12148. parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
  12149. parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
  12150. each(previewStyles.split(' '), function(name) {
  12151. var value = dom.getStyle(previewElm, name, true);
  12152. // If background is transparent then check if the body has a background color we can use
  12153. if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
  12154. value = dom.getStyle(editor.getBody(), name, true);
  12155. // Ignore white since it's the default color, not the nicest fix
  12156. // TODO: Fix this by detecting runtime style
  12157. if (dom.toHex(value).toLowerCase() == '#ffffff') {
  12158. return;
  12159. }
  12160. }
  12161. if (name == 'color') {
  12162. // Ignore black since it's the default color, not the nicest fix
  12163. // TODO: Fix this by detecting runtime style
  12164. if (dom.toHex(value).toLowerCase() == '#000000') {
  12165. return;
  12166. }
  12167. }
  12168. // Old IE won't calculate the font size so we need to do that manually
  12169. if (name == 'font-size') {
  12170. if (/em|%$/.test(value)) {
  12171. if (parentFontSize === 0) {
  12172. return;
  12173. }
  12174. // Convert font size from em/% to px
  12175. value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
  12176. value = (value * parentFontSize) + 'px';
  12177. }
  12178. }
  12179. if (name == "border" && value) {
  12180. previewCss += 'padding:0 2px;';
  12181. }
  12182. previewCss += name + ':' + value + ';';
  12183. });
  12184. editor.fire('AfterPreviewFormats');
  12185. //previewCss += 'line-height:normal';
  12186. dom.remove(previewElm);
  12187. return previewCss;
  12188. }
  12189. return {
  12190. getCssText: getCssText
  12191. };
  12192. });
  12193. // Included from: js/tinymce/classes/Formatter.js
  12194. /**
  12195. * Formatter.js
  12196. *
  12197. * Copyright, Moxiecode Systems AB
  12198. * Released under LGPL License.
  12199. *
  12200. * License: http://www.tinymce.com/license
  12201. * Contributing: http://www.tinymce.com/contributing
  12202. */
  12203. /**
  12204. * Text formatter engine class. This class is used to apply formats like bold, italic, font size
  12205. * etc to the current selection or specific nodes. This engine was build to replace the browsers
  12206. * default formatting logic for execCommand due to it's inconsistent and buggy behavior.
  12207. *
  12208. * @class tinymce.Formatter
  12209. * @example
  12210. * tinymce.activeEditor.formatter.register('mycustomformat', {
  12211. * inline: 'span',
  12212. * styles: {color: '#ff0000'}
  12213. * });
  12214. *
  12215. * tinymce.activeEditor.formatter.apply('mycustomformat');
  12216. */
  12217. define("tinymce/Formatter", [
  12218. "tinymce/dom/TreeWalker",
  12219. "tinymce/dom/RangeUtils",
  12220. "tinymce/util/Tools",
  12221. "tinymce/fmt/Preview"
  12222. ], function(TreeWalker, RangeUtils, Tools, Preview) {
  12223. /**
  12224. * Constructs a new formatter instance.
  12225. *
  12226. * @constructor Formatter
  12227. * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
  12228. */
  12229. return function(ed) {
  12230. var formats = {},
  12231. dom = ed.dom,
  12232. selection = ed.selection,
  12233. rangeUtils = new RangeUtils(dom),
  12234. isValid = ed.schema.isValidChild,
  12235. isBlock = dom.isBlock,
  12236. forcedRootBlock = ed.settings.forced_root_block,
  12237. nodeIndex = dom.nodeIndex,
  12238. INVISIBLE_CHAR = '\uFEFF',
  12239. MCE_ATTR_RE = /^(src|href|style)$/,
  12240. FALSE = false,
  12241. TRUE = true,
  12242. formatChangeData,
  12243. undef,
  12244. getContentEditable = dom.getContentEditable,
  12245. disableCaretContainer,
  12246. markCaretContainersBogus;
  12247. var each = Tools.each,
  12248. grep = Tools.grep,
  12249. walk = Tools.walk,
  12250. extend = Tools.extend;
  12251. function isTextBlock(name) {
  12252. if (name.nodeType) {
  12253. name = name.nodeName;
  12254. }
  12255. return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
  12256. }
  12257. function getParents(node, selector) {
  12258. return dom.getParents(node, selector, dom.getRoot());
  12259. }
  12260. function isCaretNode(node) {
  12261. return node.nodeType === 1 && node.id === '_mce_caret';
  12262. }
  12263. function defaultFormats() {
  12264. register({
  12265. alignleft: [
  12266. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'left'}, defaultBlock: 'div'},
  12267. {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
  12268. ],
  12269. aligncenter: [
  12270. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'center'}, defaultBlock: 'div'},
  12271. {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
  12272. {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
  12273. ],
  12274. alignright: [
  12275. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'right'}, defaultBlock: 'div'},
  12276. {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
  12277. ],
  12278. alignjustify: [
  12279. {selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li', styles: {textAlign: 'justify'}, defaultBlock: 'div'}
  12280. ],
  12281. bold: [
  12282. {inline: 'strong', remove: 'all'},
  12283. {inline: 'span', styles: {fontWeight: 'bold'}},
  12284. {inline: 'b', remove: 'all'}
  12285. ],
  12286. italic: [
  12287. {inline: 'em', remove: 'all'},
  12288. {inline: 'span', styles: {fontStyle: 'italic'}},
  12289. {inline: 'i', remove: 'all'}
  12290. ],
  12291. underline: [
  12292. {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
  12293. {inline: 'u', remove: 'all'}
  12294. ],
  12295. strikethrough: [
  12296. {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
  12297. {inline: 'strike', remove: 'all'}
  12298. ],
  12299. forecolor: {inline: 'span', styles: {color: '%value'}, wrap_links: false},
  12300. hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, wrap_links: false},
  12301. fontname: {inline: 'span', styles: {fontFamily: '%value'}},
  12302. fontsize: {inline: 'span', styles: {fontSize: '%value'}},
  12303. fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
  12304. blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
  12305. subscript: {inline: 'sub'},
  12306. superscript: {inline: 'sup'},
  12307. code: {inline: 'code'},
  12308. link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
  12309. onmatch: function() {
  12310. return true;
  12311. },
  12312. onformat: function(elm, fmt, vars) {
  12313. each(vars, function(value, key) {
  12314. dom.setAttrib(elm, key, value);
  12315. });
  12316. }
  12317. },
  12318. removeformat: [
  12319. {
  12320. selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
  12321. remove: 'all',
  12322. split: true,
  12323. expand: false,
  12324. block_expand: true,
  12325. deep: true
  12326. },
  12327. {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
  12328. {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
  12329. ]
  12330. });
  12331. // Register default block formats
  12332. each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
  12333. register(name, {block: name, remove: 'all'});
  12334. });
  12335. // Register user defined formats
  12336. register(ed.settings.formats);
  12337. }
  12338. function addKeyboardShortcuts() {
  12339. // Add some inline shortcuts
  12340. ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
  12341. ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
  12342. ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
  12343. // BlockFormat shortcuts keys
  12344. for (var i = 1; i <= 6; i++) {
  12345. ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
  12346. }
  12347. ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
  12348. ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
  12349. ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
  12350. }
  12351. // Public functions
  12352. /**
  12353. * Returns the format by name or all formats if no name is specified.
  12354. *
  12355. * @method get
  12356. * @param {String} name Optional name to retrive by.
  12357. * @return {Array/Object} Array/Object with all registred formats or a specific format.
  12358. */
  12359. function get(name) {
  12360. return name ? formats[name] : formats;
  12361. }
  12362. /**
  12363. * Registers a specific format by name.
  12364. *
  12365. * @method register
  12366. * @param {Object/String} name Name of the format for example "bold".
  12367. * @param {Object/Array} format Optional format object or array of format variants
  12368. * can only be omitted if the first arg is an object.
  12369. */
  12370. function register(name, format) {
  12371. if (name) {
  12372. if (typeof(name) !== 'string') {
  12373. each(name, function(format, name) {
  12374. register(name, format);
  12375. });
  12376. } else {
  12377. // Force format into array and add it to internal collection
  12378. format = format.length ? format : [format];
  12379. each(format, function(format) {
  12380. // Set deep to false by default on selector formats this to avoid removing
  12381. // alignment on images inside paragraphs when alignment is changed on paragraphs
  12382. if (format.deep === undef) {
  12383. format.deep = !format.selector;
  12384. }
  12385. // Default to true
  12386. if (format.split === undef) {
  12387. format.split = !format.selector || format.inline;
  12388. }
  12389. // Default to true
  12390. if (format.remove === undef && format.selector && !format.inline) {
  12391. format.remove = 'none';
  12392. }
  12393. // Mark format as a mixed format inline + block level
  12394. if (format.selector && format.inline) {
  12395. format.mixed = true;
  12396. format.block_expand = true;
  12397. }
  12398. // Split classes if needed
  12399. if (typeof(format.classes) === 'string') {
  12400. format.classes = format.classes.split(/\s+/);
  12401. }
  12402. });
  12403. formats[name] = format;
  12404. }
  12405. }
  12406. }
  12407. function getTextDecoration(node) {
  12408. var decoration;
  12409. ed.dom.getParent(node, function(n) {
  12410. decoration = ed.dom.getStyle(n, 'text-decoration');
  12411. return decoration && decoration !== 'none';
  12412. });
  12413. return decoration;
  12414. }
  12415. function processUnderlineAndColor(node) {
  12416. var textDecoration;
  12417. if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
  12418. textDecoration = getTextDecoration(node.parentNode);
  12419. if (ed.dom.getStyle(node, 'color') && textDecoration) {
  12420. ed.dom.setStyle(node, 'text-decoration', textDecoration);
  12421. } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
  12422. ed.dom.setStyle(node, 'text-decoration', null);
  12423. }
  12424. }
  12425. }
  12426. /**
  12427. * Applies the specified format to the current selection or specified node.
  12428. *
  12429. * @method apply
  12430. * @param {String} name Name of format to apply.
  12431. * @param {Object} vars Optional list of variables to replace within format before applying it.
  12432. * @param {Node} node Optional node to apply the format to defaults to current selection.
  12433. */
  12434. function apply(name, vars, node) {
  12435. var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
  12436. function setElementFormat(elm, fmt) {
  12437. fmt = fmt || format;
  12438. if (elm) {
  12439. if (fmt.onformat) {
  12440. fmt.onformat(elm, fmt, vars, node);
  12441. }
  12442. each(fmt.styles, function(value, name) {
  12443. dom.setStyle(elm, name, replaceVars(value, vars));
  12444. });
  12445. each(fmt.attributes, function(value, name) {
  12446. dom.setAttrib(elm, name, replaceVars(value, vars));
  12447. });
  12448. each(fmt.classes, function(value) {
  12449. value = replaceVars(value, vars);
  12450. if (!dom.hasClass(elm, value)) {
  12451. dom.addClass(elm, value);
  12452. }
  12453. });
  12454. }
  12455. }
  12456. function adjustSelectionToVisibleSelection() {
  12457. function findSelectionEnd(start, end) {
  12458. var walker = new TreeWalker(end);
  12459. for (node = walker.current(); node; node = walker.prev()) {
  12460. if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
  12461. return node;
  12462. }
  12463. }
  12464. }
  12465. // Adjust selection so that a end container with a end offset of zero is not included in the selection
  12466. // as this isn't visible to the user.
  12467. var rng = ed.selection.getRng();
  12468. var start = rng.startContainer;
  12469. var end = rng.endContainer;
  12470. if (start != end && rng.endOffset === 0) {
  12471. var newEnd = findSelectionEnd(start, end);
  12472. var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
  12473. rng.setEnd(newEnd, endOffset);
  12474. }
  12475. return rng;
  12476. }
  12477. function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
  12478. var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
  12479. // find the index of the first child list.
  12480. each(node.childNodes, function(n, index) {
  12481. if (n.nodeName === "UL" || n.nodeName === "OL") {
  12482. listIndex = index;
  12483. list = n;
  12484. return false;
  12485. }
  12486. });
  12487. // get the index of the bookmarks
  12488. each(node.childNodes, function(n, index) {
  12489. if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
  12490. if (n.id == bookmark.id + "_start") {
  12491. startIndex = index;
  12492. } else if (n.id == bookmark.id + "_end") {
  12493. endIndex = index;
  12494. }
  12495. }
  12496. });
  12497. // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
  12498. if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
  12499. each(grep(node.childNodes), process);
  12500. return 0;
  12501. } else {
  12502. currentWrapElm = dom.clone(wrapElm, FALSE);
  12503. // create a list of the nodes on the same side of the list as the selection
  12504. each(grep(node.childNodes), function(n, index) {
  12505. if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
  12506. nodes.push(n);
  12507. n.parentNode.removeChild(n);
  12508. }
  12509. });
  12510. // insert the wrapping element either before or after the list.
  12511. if (startIndex < listIndex) {
  12512. node.insertBefore(currentWrapElm, list);
  12513. } else if (startIndex > listIndex) {
  12514. node.insertBefore(currentWrapElm, list.nextSibling);
  12515. }
  12516. // add the new nodes to the list.
  12517. newWrappers.push(currentWrapElm);
  12518. each(nodes, function(node) {
  12519. currentWrapElm.appendChild(node);
  12520. });
  12521. return currentWrapElm;
  12522. }
  12523. }
  12524. function applyRngStyle(rng, bookmark, node_specific) {
  12525. var newWrappers = [], wrapName, wrapElm, contentEditable = true;
  12526. // Setup wrapper element
  12527. wrapName = format.inline || format.block;
  12528. wrapElm = dom.create(wrapName);
  12529. setElementFormat(wrapElm);
  12530. rangeUtils.walk(rng, function(nodes) {
  12531. var currentWrapElm;
  12532. /**
  12533. * Process a list of nodes wrap them.
  12534. */
  12535. function process(node) {
  12536. var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
  12537. lastContentEditable = contentEditable;
  12538. nodeName = node.nodeName.toLowerCase();
  12539. parentName = node.parentNode.nodeName.toLowerCase();
  12540. // Node has a contentEditable value
  12541. if (node.nodeType === 1 && getContentEditable(node)) {
  12542. lastContentEditable = contentEditable;
  12543. contentEditable = getContentEditable(node) === "true";
  12544. hasContentEditableState = true; // We don't want to wrap the container only it's children
  12545. }
  12546. // Stop wrapping on br elements
  12547. if (isEq(nodeName, 'br')) {
  12548. currentWrapElm = 0;
  12549. // Remove any br elements when we wrap things
  12550. if (format.block) {
  12551. dom.remove(node);
  12552. }
  12553. return;
  12554. }
  12555. // If node is wrapper type
  12556. if (format.wrapper && matchNode(node, name, vars)) {
  12557. currentWrapElm = 0;
  12558. return;
  12559. }
  12560. // Can we rename the block
  12561. // TODO: Break this if up, too complex
  12562. if (contentEditable && !hasContentEditableState && format.block &&
  12563. !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
  12564. node = dom.rename(node, wrapName);
  12565. setElementFormat(node);
  12566. newWrappers.push(node);
  12567. currentWrapElm = 0;
  12568. return;
  12569. }
  12570. // Handle selector patterns
  12571. if (format.selector) {
  12572. // Look for matching formats
  12573. each(formatList, function(format) {
  12574. // Check collapsed state if it exists
  12575. if ('collapsed' in format && format.collapsed !== isCollapsed) {
  12576. return;
  12577. }
  12578. if (dom.is(node, format.selector) && !isCaretNode(node)) {
  12579. setElementFormat(node, format);
  12580. found = true;
  12581. }
  12582. });
  12583. // Continue processing if a selector match wasn't found and a inline element is defined
  12584. if (!format.inline || found) {
  12585. currentWrapElm = 0;
  12586. return;
  12587. }
  12588. }
  12589. // Is it valid to wrap this item
  12590. // TODO: Break this if up, too complex
  12591. if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
  12592. !(!node_specific && node.nodeType === 3 &&
  12593. node.nodeValue.length === 1 &&
  12594. node.nodeValue.charCodeAt(0) === 65279) &&
  12595. !isCaretNode(node) &&
  12596. (!format.inline || !isBlock(node))) {
  12597. // Start wrapping
  12598. if (!currentWrapElm) {
  12599. // Wrap the node
  12600. currentWrapElm = dom.clone(wrapElm, FALSE);
  12601. node.parentNode.insertBefore(currentWrapElm, node);
  12602. newWrappers.push(currentWrapElm);
  12603. }
  12604. currentWrapElm.appendChild(node);
  12605. } else if (nodeName == 'li' && bookmark) {
  12606. // Start wrapping - if we are in a list node and have a bookmark, then
  12607. // we will always begin by wrapping in a new element.
  12608. currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
  12609. } else {
  12610. // Start a new wrapper for possible children
  12611. currentWrapElm = 0;
  12612. each(grep(node.childNodes), process);
  12613. if (hasContentEditableState) {
  12614. contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  12615. }
  12616. // End the last wrapper
  12617. currentWrapElm = 0;
  12618. }
  12619. }
  12620. // Process siblings from range
  12621. each(nodes, process);
  12622. });
  12623. // Wrap links inside as well, for example color inside a link when the wrapper is around the link
  12624. if (format.wrap_links === false) {
  12625. each(newWrappers, function(node) {
  12626. function process(node) {
  12627. var i, currentWrapElm, children;
  12628. if (node.nodeName === 'A') {
  12629. currentWrapElm = dom.clone(wrapElm, FALSE);
  12630. newWrappers.push(currentWrapElm);
  12631. children = grep(node.childNodes);
  12632. for (i = 0; i < children.length; i++) {
  12633. currentWrapElm.appendChild(children[i]);
  12634. }
  12635. node.appendChild(currentWrapElm);
  12636. }
  12637. each(grep(node.childNodes), process);
  12638. }
  12639. process(node);
  12640. });
  12641. }
  12642. // Cleanup
  12643. each(newWrappers, function(node) {
  12644. var childCount;
  12645. function getChildCount(node) {
  12646. var count = 0;
  12647. each(node.childNodes, function(node) {
  12648. if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
  12649. count++;
  12650. }
  12651. });
  12652. return count;
  12653. }
  12654. function mergeStyles(node) {
  12655. var child, clone;
  12656. each(node.childNodes, function(node) {
  12657. if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
  12658. child = node;
  12659. return FALSE; // break loop
  12660. }
  12661. });
  12662. // If child was found and of the same type as the current node
  12663. if (child && !isBookmarkNode(child) && matchName(child, format)) {
  12664. clone = dom.clone(child, FALSE);
  12665. setElementFormat(clone);
  12666. dom.replace(clone, node, TRUE);
  12667. dom.remove(child, 1);
  12668. }
  12669. return clone || node;
  12670. }
  12671. childCount = getChildCount(node);
  12672. // Remove empty nodes but only if there is multiple wrappers and they are not block
  12673. // elements so never remove single <h1></h1> since that would remove the
  12674. // currrent empty block element where the caret is at
  12675. if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
  12676. dom.remove(node, 1);
  12677. return;
  12678. }
  12679. if (format.inline || format.wrapper) {
  12680. // Merges the current node with it's children of similar type to reduce the number of elements
  12681. if (!format.exact && childCount === 1) {
  12682. node = mergeStyles(node);
  12683. }
  12684. // Remove/merge children
  12685. each(formatList, function(format) {
  12686. // Merge all children of similar type will move styles from child to parent
  12687. // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
  12688. // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
  12689. each(dom.select(format.inline, node), function(child) {
  12690. var parent;
  12691. if (isBookmarkNode(child)) {
  12692. return;
  12693. }
  12694. // When wrap_links is set to false we don't want
  12695. // to remove the format on children within links
  12696. if (format.wrap_links === false) {
  12697. parent = child.parentNode;
  12698. do {
  12699. if (parent.nodeName === 'A') {
  12700. return;
  12701. }
  12702. } while ((parent = parent.parentNode));
  12703. }
  12704. removeFormat(format, vars, child, format.exact ? child : null);
  12705. });
  12706. });
  12707. // Remove child if direct parent is of same type
  12708. if (matchNode(node.parentNode, name, vars)) {
  12709. dom.remove(node, 1);
  12710. node = 0;
  12711. return TRUE;
  12712. }
  12713. // Look for parent with similar style format
  12714. if (format.merge_with_parents) {
  12715. dom.getParent(node.parentNode, function(parent) {
  12716. if (matchNode(parent, name, vars)) {
  12717. dom.remove(node, 1);
  12718. node = 0;
  12719. return TRUE;
  12720. }
  12721. });
  12722. }
  12723. // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
  12724. if (node && format.merge_siblings !== false) {
  12725. node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
  12726. node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
  12727. }
  12728. }
  12729. });
  12730. }
  12731. if (format) {
  12732. if (node) {
  12733. if (node.nodeType) {
  12734. rng = dom.createRng();
  12735. rng.setStartBefore(node);
  12736. rng.setEndAfter(node);
  12737. applyRngStyle(expandRng(rng, formatList), null, true);
  12738. } else {
  12739. applyRngStyle(node, null, true);
  12740. }
  12741. } else {
  12742. if (!isCollapsed || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
  12743. // Obtain selection node before selection is unselected by applyRngStyle()
  12744. var curSelNode = ed.selection.getNode();
  12745. // If the formats have a default block and we can't find a parent block then
  12746. // start wrapping it with a DIV this is for forced_root_blocks: false
  12747. // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
  12748. if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
  12749. apply(formatList[0].defaultBlock);
  12750. }
  12751. // Apply formatting to selection
  12752. ed.selection.setRng(adjustSelectionToVisibleSelection());
  12753. bookmark = selection.getBookmark();
  12754. applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
  12755. // Colored nodes should be underlined so that the color of the underline matches the text color.
  12756. if (format.styles && (format.styles.color || format.styles.textDecoration)) {
  12757. walk(curSelNode, processUnderlineAndColor, 'childNodes');
  12758. processUnderlineAndColor(curSelNode);
  12759. }
  12760. selection.moveToBookmark(bookmark);
  12761. moveStart(selection.getRng(TRUE));
  12762. ed.nodeChanged();
  12763. } else {
  12764. performCaretAction('apply', name, vars);
  12765. }
  12766. }
  12767. }
  12768. }
  12769. /**
  12770. * Removes the specified format from the current selection or specified node.
  12771. *
  12772. * @method remove
  12773. * @param {String} name Name of format to remove.
  12774. * @param {Object} vars Optional list of variables to replace within format before removing it.
  12775. * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
  12776. */
  12777. function remove(name, vars, node) {
  12778. var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
  12779. // Merges the styles for each node
  12780. function process(node) {
  12781. var children, i, l, lastContentEditable, hasContentEditableState;
  12782. // Node has a contentEditable value
  12783. if (node.nodeType === 1 && getContentEditable(node)) {
  12784. lastContentEditable = contentEditable;
  12785. contentEditable = getContentEditable(node) === "true";
  12786. hasContentEditableState = true; // We don't want to wrap the container only it's children
  12787. }
  12788. // Grab the children first since the nodelist might be changed
  12789. children = grep(node.childNodes);
  12790. // Process current node
  12791. if (contentEditable && !hasContentEditableState) {
  12792. for (i = 0, l = formatList.length; i < l; i++) {
  12793. if (removeFormat(formatList[i], vars, node, node)) {
  12794. break;
  12795. }
  12796. }
  12797. }
  12798. // Process the children
  12799. if (format.deep) {
  12800. if (children.length) {
  12801. for (i = 0, l = children.length; i < l; i++) {
  12802. process(children[i]);
  12803. }
  12804. if (hasContentEditableState) {
  12805. contentEditable = lastContentEditable; // Restore last contentEditable state from stack
  12806. }
  12807. }
  12808. }
  12809. }
  12810. function findFormatRoot(container) {
  12811. var formatRoot;
  12812. // Find format root
  12813. each(getParents(container.parentNode).reverse(), function(parent) {
  12814. var format;
  12815. // Find format root element
  12816. if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
  12817. // Is the node matching the format we are looking for
  12818. format = matchNode(parent, name, vars);
  12819. if (format && format.split !== false) {
  12820. formatRoot = parent;
  12821. }
  12822. }
  12823. });
  12824. return formatRoot;
  12825. }
  12826. function wrapAndSplit(format_root, container, target, split) {
  12827. var parent, clone, lastClone, firstClone, i, formatRootParent;
  12828. // Format root found then clone formats and split it
  12829. if (format_root) {
  12830. formatRootParent = format_root.parentNode;
  12831. for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
  12832. clone = dom.clone(parent, FALSE);
  12833. for (i = 0; i < formatList.length; i++) {
  12834. if (removeFormat(formatList[i], vars, clone, clone)) {
  12835. clone = 0;
  12836. break;
  12837. }
  12838. }
  12839. // Build wrapper node
  12840. if (clone) {
  12841. if (lastClone) {
  12842. clone.appendChild(lastClone);
  12843. }
  12844. if (!firstClone) {
  12845. firstClone = clone;
  12846. }
  12847. lastClone = clone;
  12848. }
  12849. }
  12850. // Never split block elements if the format is mixed
  12851. if (split && (!format.mixed || !isBlock(format_root))) {
  12852. container = dom.split(format_root, container);
  12853. }
  12854. // Wrap container in cloned formats
  12855. if (lastClone) {
  12856. target.parentNode.insertBefore(lastClone, target);
  12857. firstClone.appendChild(target);
  12858. }
  12859. }
  12860. return container;
  12861. }
  12862. function splitToFormatRoot(container) {
  12863. return wrapAndSplit(findFormatRoot(container), container, container, true);
  12864. }
  12865. function unwrap(start) {
  12866. var node = dom.get(start ? '_start' : '_end'),
  12867. out = node[start ? 'firstChild' : 'lastChild'];
  12868. // If the end is placed within the start the result will be removed
  12869. // So this checks if the out node is a bookmark node if it is it
  12870. // checks for another more suitable node
  12871. if (isBookmarkNode(out)) {
  12872. out = out[start ? 'firstChild' : 'lastChild'];
  12873. }
  12874. dom.remove(node, true);
  12875. return out;
  12876. }
  12877. function removeRngStyle(rng) {
  12878. var startContainer, endContainer;
  12879. var commonAncestorContainer = rng.commonAncestorContainer;
  12880. rng = expandRng(rng, formatList, TRUE);
  12881. if (format.split) {
  12882. startContainer = getContainer(rng, TRUE);
  12883. endContainer = getContainer(rng);
  12884. if (startContainer != endContainer) {
  12885. // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
  12886. // so let's see if we can use the first child instead
  12887. // This will happen if you triple click a table cell and use remove formatting
  12888. if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
  12889. if (startContainer.nodeName == "TR") {
  12890. startContainer = startContainer.firstChild.firstChild || startContainer;
  12891. } else {
  12892. startContainer = startContainer.firstChild || startContainer;
  12893. }
  12894. }
  12895. // Try to adjust endContainer as well if cells on the same row were selected - bug #6410
  12896. if (commonAncestorContainer &&
  12897. /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
  12898. /^(TH|TD)$/.test(endContainer.nodeName) && endContainer.firstChild) {
  12899. endContainer = endContainer.firstChild || endContainer;
  12900. }
  12901. // Wrap start/end nodes in span element since these might be cloned/moved
  12902. startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
  12903. endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
  12904. // Split start/end
  12905. splitToFormatRoot(startContainer);
  12906. splitToFormatRoot(endContainer);
  12907. // Unwrap start/end to get real elements again
  12908. startContainer = unwrap(TRUE);
  12909. endContainer = unwrap();
  12910. } else {
  12911. startContainer = endContainer = splitToFormatRoot(startContainer);
  12912. }
  12913. // Update range positions since they might have changed after the split operations
  12914. rng.startContainer = startContainer.parentNode;
  12915. rng.startOffset = nodeIndex(startContainer);
  12916. rng.endContainer = endContainer.parentNode;
  12917. rng.endOffset = nodeIndex(endContainer) + 1;
  12918. }
  12919. // Remove items between start/end
  12920. rangeUtils.walk(rng, function(nodes) {
  12921. each(nodes, function(node) {
  12922. process(node);
  12923. // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
  12924. if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
  12925. node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
  12926. removeFormat({
  12927. 'deep': false,
  12928. 'exact': true,
  12929. 'inline': 'span',
  12930. 'styles': {
  12931. 'textDecoration': 'underline'
  12932. }
  12933. }, null, node);
  12934. }
  12935. });
  12936. });
  12937. }
  12938. // Handle node
  12939. if (node) {
  12940. if (node.nodeType) {
  12941. rng = dom.createRng();
  12942. rng.setStartBefore(node);
  12943. rng.setEndAfter(node);
  12944. removeRngStyle(rng);
  12945. } else {
  12946. removeRngStyle(node);
  12947. }
  12948. return;
  12949. }
  12950. if (!selection.isCollapsed() || !format.inline || dom.select('td.mce-item-selected,th.mce-item-selected').length) {
  12951. bookmark = selection.getBookmark();
  12952. removeRngStyle(selection.getRng(TRUE));
  12953. selection.moveToBookmark(bookmark);
  12954. // Check if start element still has formatting then we are at: "<b>text|</b>text"
  12955. // and need to move the start into the next text node
  12956. if (format.inline && match(name, vars, selection.getStart())) {
  12957. moveStart(selection.getRng(true));
  12958. }
  12959. ed.nodeChanged();
  12960. } else {
  12961. performCaretAction('remove', name, vars);
  12962. }
  12963. }
  12964. /**
  12965. * Toggles the specified format on/off.
  12966. *
  12967. * @method toggle
  12968. * @param {String} name Name of format to apply/remove.
  12969. * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
  12970. * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
  12971. */
  12972. function toggle(name, vars, node) {
  12973. var fmt = get(name);
  12974. if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
  12975. remove(name, vars, node);
  12976. } else {
  12977. apply(name, vars, node);
  12978. }
  12979. }
  12980. /**
  12981. * Return true/false if the specified node has the specified format.
  12982. *
  12983. * @method matchNode
  12984. * @param {Node} node Node to check the format on.
  12985. * @param {String} name Format name to check.
  12986. * @param {Object} vars Optional list of variables to replace before checking it.
  12987. * @param {Boolean} similar Match format that has similar properties.
  12988. * @return {Object} Returns the format object it matches or undefined if it doesn't match.
  12989. */
  12990. function matchNode(node, name, vars, similar) {
  12991. var formatList = get(name), format, i, classes;
  12992. function matchItems(node, format, item_name) {
  12993. var key, value, items = format[item_name], i;
  12994. // Custom match
  12995. if (format.onmatch) {
  12996. return format.onmatch(node, format, item_name);
  12997. }
  12998. // Check all items
  12999. if (items) {
  13000. // Non indexed object
  13001. if (items.length === undef) {
  13002. for (key in items) {
  13003. if (items.hasOwnProperty(key)) {
  13004. if (item_name === 'attributes') {
  13005. value = dom.getAttrib(node, key);
  13006. } else {
  13007. value = getStyle(node, key);
  13008. }
  13009. if (similar && !value && !format.exact) {
  13010. return;
  13011. }
  13012. if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
  13013. return;
  13014. }
  13015. }
  13016. }
  13017. } else {
  13018. // Only one match needed for indexed arrays
  13019. for (i = 0; i < items.length; i++) {
  13020. if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
  13021. return format;
  13022. }
  13023. }
  13024. }
  13025. }
  13026. return format;
  13027. }
  13028. if (formatList && node) {
  13029. // Check each format in list
  13030. for (i = 0; i < formatList.length; i++) {
  13031. format = formatList[i];
  13032. // Name name, attributes, styles and classes
  13033. if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
  13034. // Match classes
  13035. if ((classes = format.classes)) {
  13036. for (i = 0; i < classes.length; i++) {
  13037. if (!dom.hasClass(node, classes[i])) {
  13038. return;
  13039. }
  13040. }
  13041. }
  13042. return format;
  13043. }
  13044. }
  13045. }
  13046. }
  13047. /**
  13048. * Matches the current selection or specified node against the specified format name.
  13049. *
  13050. * @method match
  13051. * @param {String} name Name of format to match.
  13052. * @param {Object} vars Optional list of variables to replace before checking it.
  13053. * @param {Node} node Optional node to check.
  13054. * @return {boolean} true/false if the specified selection/node matches the format.
  13055. */
  13056. function match(name, vars, node) {
  13057. var startNode;
  13058. function matchParents(node) {
  13059. var root = dom.getRoot();
  13060. if (node === root) {
  13061. return false;
  13062. }
  13063. // Find first node with similar format settings
  13064. node = dom.getParent(node, function(node) {
  13065. return node.parentNode === root || !!matchNode(node, name, vars, true);
  13066. });
  13067. // Do an exact check on the similar format element
  13068. return matchNode(node, name, vars);
  13069. }
  13070. // Check specified node
  13071. if (node) {
  13072. return matchParents(node);
  13073. }
  13074. // Check selected node
  13075. node = selection.getNode();
  13076. if (matchParents(node)) {
  13077. return TRUE;
  13078. }
  13079. // Check start node if it's different
  13080. startNode = selection.getStart();
  13081. if (startNode != node) {
  13082. if (matchParents(startNode)) {
  13083. return TRUE;
  13084. }
  13085. }
  13086. return FALSE;
  13087. }
  13088. /**
  13089. * Matches the current selection against the array of formats and returns a new array with matching formats.
  13090. *
  13091. * @method matchAll
  13092. * @param {Array} names Name of format to match.
  13093. * @param {Object} vars Optional list of variables to replace before checking it.
  13094. * @return {Array} Array with matched formats.
  13095. */
  13096. function matchAll(names, vars) {
  13097. var startElement, matchedFormatNames = [], checkedMap = {};
  13098. // Check start of selection for formats
  13099. startElement = selection.getStart();
  13100. dom.getParent(startElement, function(node) {
  13101. var i, name;
  13102. for (i = 0; i < names.length; i++) {
  13103. name = names[i];
  13104. if (!checkedMap[name] && matchNode(node, name, vars)) {
  13105. checkedMap[name] = true;
  13106. matchedFormatNames.push(name);
  13107. }
  13108. }
  13109. }, dom.getRoot());
  13110. return matchedFormatNames;
  13111. }
  13112. /**
  13113. * Returns true/false if the specified format can be applied to the current selection or not. It
  13114. * will currently only check the state for selector formats, it returns true on all other format types.
  13115. *
  13116. * @method canApply
  13117. * @param {String} name Name of format to check.
  13118. * @return {boolean} true/false if the specified format can be applied to the current selection/node.
  13119. */
  13120. function canApply(name) {
  13121. var formatList = get(name), startNode, parents, i, x, selector;
  13122. if (formatList) {
  13123. startNode = selection.getStart();
  13124. parents = getParents(startNode);
  13125. for (x = formatList.length - 1; x >= 0; x--) {
  13126. selector = formatList[x].selector;
  13127. // Format is not selector based then always return TRUE
  13128. // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
  13129. if (!selector || formatList[x].defaultBlock) {
  13130. return TRUE;
  13131. }
  13132. for (i = parents.length - 1; i >= 0; i--) {
  13133. if (dom.is(parents[i], selector)) {
  13134. return TRUE;
  13135. }
  13136. }
  13137. }
  13138. }
  13139. return FALSE;
  13140. }
  13141. /**
  13142. * Executes the specified callback when the current selection matches the formats or not.
  13143. *
  13144. * @method formatChanged
  13145. * @param {String} formats Comma separated list of formats to check for.
  13146. * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
  13147. * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
  13148. */
  13149. function formatChanged(formats, callback, similar) {
  13150. var currentFormats;
  13151. // Setup format node change logic
  13152. if (!formatChangeData) {
  13153. formatChangeData = {};
  13154. currentFormats = {};
  13155. ed.on('NodeChange', function(e) {
  13156. var parents = getParents(e.element), matchedFormats = {};
  13157. // Check for new formats
  13158. each(formatChangeData, function(callbacks, format) {
  13159. each(parents, function(node) {
  13160. if (matchNode(node, format, {}, callbacks.similar)) {
  13161. if (!currentFormats[format]) {
  13162. // Execute callbacks
  13163. each(callbacks, function(callback) {
  13164. callback(true, {node: node, format: format, parents: parents});
  13165. });
  13166. currentFormats[format] = callbacks;
  13167. }
  13168. matchedFormats[format] = callbacks;
  13169. return false;
  13170. }
  13171. });
  13172. });
  13173. // Check if current formats still match
  13174. each(currentFormats, function(callbacks, format) {
  13175. if (!matchedFormats[format]) {
  13176. delete currentFormats[format];
  13177. each(callbacks, function(callback) {
  13178. callback(false, {node: e.element, format: format, parents: parents});
  13179. });
  13180. }
  13181. });
  13182. });
  13183. }
  13184. // Add format listeners
  13185. each(formats.split(','), function(format) {
  13186. if (!formatChangeData[format]) {
  13187. formatChangeData[format] = [];
  13188. formatChangeData[format].similar = similar;
  13189. }
  13190. formatChangeData[format].push(callback);
  13191. });
  13192. return this;
  13193. }
  13194. /**
  13195. * Returns a preview css text for the specified format.
  13196. *
  13197. * @method getCssText
  13198. * @param {String/Object} format Format to generate preview css text for.
  13199. * @return {String} Css text for the specified format.
  13200. * @example
  13201. * var cssText1 = editor.formatter.getCssText('bold');
  13202. * var cssText2 = editor.formatter.getCssText({inline: 'b'});
  13203. */
  13204. function getCssText(format) {
  13205. return Preview.getCssText(ed, format);
  13206. }
  13207. // Expose to public
  13208. extend(this, {
  13209. get: get,
  13210. register: register,
  13211. apply: apply,
  13212. remove: remove,
  13213. toggle: toggle,
  13214. match: match,
  13215. matchAll: matchAll,
  13216. matchNode: matchNode,
  13217. canApply: canApply,
  13218. formatChanged: formatChanged,
  13219. getCssText: getCssText
  13220. });
  13221. // Initialize
  13222. defaultFormats();
  13223. addKeyboardShortcuts();
  13224. ed.on('BeforeGetContent', function() {
  13225. if (markCaretContainersBogus) {
  13226. markCaretContainersBogus();
  13227. }
  13228. });
  13229. ed.on('mouseup keydown', function(e) {
  13230. if (disableCaretContainer) {
  13231. disableCaretContainer(e);
  13232. }
  13233. });
  13234. // Private functions
  13235. /**
  13236. * Checks if the specified nodes name matches the format inline/block or selector.
  13237. *
  13238. * @private
  13239. * @param {Node} node Node to match against the specified format.
  13240. * @param {Object} format Format object o match with.
  13241. * @return {boolean} true/false if the format matches.
  13242. */
  13243. function matchName(node, format) {
  13244. // Check for inline match
  13245. if (isEq(node, format.inline)) {
  13246. return TRUE;
  13247. }
  13248. // Check for block match
  13249. if (isEq(node, format.block)) {
  13250. return TRUE;
  13251. }
  13252. // Check for selector match
  13253. if (format.selector) {
  13254. return node.nodeType == 1 && dom.is(node, format.selector);
  13255. }
  13256. }
  13257. /**
  13258. * Compares two string/nodes regardless of their case.
  13259. *
  13260. * @private
  13261. * @param {String/Node} Node or string to compare.
  13262. * @param {String/Node} Node or string to compare.
  13263. * @return {boolean} True/false if they match.
  13264. */
  13265. function isEq(str1, str2) {
  13266. str1 = str1 || '';
  13267. str2 = str2 || '';
  13268. str1 = '' + (str1.nodeName || str1);
  13269. str2 = '' + (str2.nodeName || str2);
  13270. return str1.toLowerCase() == str2.toLowerCase();
  13271. }
  13272. /**
  13273. * Returns the style by name on the specified node. This method modifies the style
  13274. * contents to make it more easy to match. This will resolve a few browser issues.
  13275. *
  13276. * @private
  13277. * @param {Node} node to get style from.
  13278. * @param {String} name Style name to get.
  13279. * @return {String} Style item value.
  13280. */
  13281. function getStyle(node, name) {
  13282. return normalizeStyleValue(dom.getStyle(node, name), name);
  13283. }
  13284. /**
  13285. * Normalize style value by name. This method modifies the style contents
  13286. * to make it more easy to match. This will resolve a few browser issues.
  13287. *
  13288. * @private
  13289. * @param {Node} node to get style from.
  13290. * @param {String} name Style name to get.
  13291. * @return {String} Style item value.
  13292. */
  13293. function normalizeStyleValue(value, name) {
  13294. // Force the format to hex
  13295. if (name == 'color' || name == 'backgroundColor') {
  13296. value = dom.toHex(value);
  13297. }
  13298. // Opera will return bold as 700
  13299. if (name == 'fontWeight' && value == 700) {
  13300. value = 'bold';
  13301. }
  13302. // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
  13303. if (name == 'fontFamily') {
  13304. value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
  13305. }
  13306. return '' + value;
  13307. }
  13308. /**
  13309. * Replaces variables in the value. The variable format is %var.
  13310. *
  13311. * @private
  13312. * @param {String} value Value to replace variables in.
  13313. * @param {Object} vars Name/value array with variables to replace.
  13314. * @return {String} New value with replaced variables.
  13315. */
  13316. function replaceVars(value, vars) {
  13317. if (typeof(value) != "string") {
  13318. value = value(vars);
  13319. } else if (vars) {
  13320. value = value.replace(/%(\w+)/g, function(str, name) {
  13321. return vars[name] || str;
  13322. });
  13323. }
  13324. return value;
  13325. }
  13326. function isWhiteSpaceNode(node) {
  13327. return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
  13328. }
  13329. function wrap(node, name, attrs) {
  13330. var wrapper = dom.create(name, attrs);
  13331. node.parentNode.insertBefore(wrapper, node);
  13332. wrapper.appendChild(node);
  13333. return wrapper;
  13334. }
  13335. /**
  13336. * Expands the specified range like object to depending on format.
  13337. *
  13338. * For example on block formats it will move the start/end position
  13339. * to the beginning of the current block.
  13340. *
  13341. * @private
  13342. * @param {Object} rng Range like object.
  13343. * @param {Array} formats Array with formats to expand by.
  13344. * @return {Object} Expanded range like object.
  13345. */
  13346. function expandRng(rng, format, remove) {
  13347. var lastIdx, leaf, endPoint,
  13348. startContainer = rng.startContainer,
  13349. startOffset = rng.startOffset,
  13350. endContainer = rng.endContainer,
  13351. endOffset = rng.endOffset;
  13352. // This function walks up the tree if there is no siblings before/after the node
  13353. function findParentContainer(start) {
  13354. var container, parent, sibling, siblingName, root;
  13355. container = parent = start ? startContainer : endContainer;
  13356. siblingName = start ? 'previousSibling' : 'nextSibling';
  13357. root = dom.getRoot();
  13358. function isBogusBr(node) {
  13359. return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
  13360. }
  13361. // If it's a text node and the offset is inside the text
  13362. if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
  13363. if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
  13364. return container;
  13365. }
  13366. }
  13367. /*eslint no-constant-condition:0 */
  13368. while (true) {
  13369. // Stop expanding on block elements
  13370. if (!format[0].block_expand && isBlock(parent)) {
  13371. return parent;
  13372. }
  13373. // Walk left/right
  13374. for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
  13375. if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
  13376. return parent;
  13377. }
  13378. }
  13379. // Check if we can move up are we at root level or body level
  13380. if (parent.parentNode == root) {
  13381. container = parent;
  13382. break;
  13383. }
  13384. parent = parent.parentNode;
  13385. }
  13386. return container;
  13387. }
  13388. // This function walks down the tree to find the leaf at the selection.
  13389. // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
  13390. function findLeaf(node, offset) {
  13391. if (offset === undef) {
  13392. offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  13393. }
  13394. while (node && node.hasChildNodes()) {
  13395. node = node.childNodes[offset];
  13396. if (node) {
  13397. offset = node.nodeType === 3 ? node.length : node.childNodes.length;
  13398. }
  13399. }
  13400. return { node: node, offset: offset };
  13401. }
  13402. // If index based start position then resolve it
  13403. if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
  13404. lastIdx = startContainer.childNodes.length - 1;
  13405. startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
  13406. if (startContainer.nodeType == 3) {
  13407. startOffset = 0;
  13408. }
  13409. }
  13410. // If index based end position then resolve it
  13411. if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
  13412. lastIdx = endContainer.childNodes.length - 1;
  13413. endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
  13414. if (endContainer.nodeType == 3) {
  13415. endOffset = endContainer.nodeValue.length;
  13416. }
  13417. }
  13418. // Expands the node to the closes contentEditable false element if it exists
  13419. function findParentContentEditable(node) {
  13420. var parent = node;
  13421. while (parent) {
  13422. if (parent.nodeType === 1 && getContentEditable(parent)) {
  13423. return getContentEditable(parent) === "false" ? parent : node;
  13424. }
  13425. parent = parent.parentNode;
  13426. }
  13427. return node;
  13428. }
  13429. function findWordEndPoint(container, offset, start) {
  13430. var walker, node, pos, lastTextNode;
  13431. function findSpace(node, offset) {
  13432. var pos, pos2, str = node.nodeValue;
  13433. if (typeof(offset) == "undefined") {
  13434. offset = start ? str.length : 0;
  13435. }
  13436. if (start) {
  13437. pos = str.lastIndexOf(' ', offset);
  13438. pos2 = str.lastIndexOf('\u00a0', offset);
  13439. pos = pos > pos2 ? pos : pos2;
  13440. // Include the space on remove to avoid tag soup
  13441. if (pos !== -1 && !remove) {
  13442. pos++;
  13443. }
  13444. } else {
  13445. pos = str.indexOf(' ', offset);
  13446. pos2 = str.indexOf('\u00a0', offset);
  13447. pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
  13448. }
  13449. return pos;
  13450. }
  13451. if (container.nodeType === 3) {
  13452. pos = findSpace(container, offset);
  13453. if (pos !== -1) {
  13454. return {container: container, offset: pos};
  13455. }
  13456. lastTextNode = container;
  13457. }
  13458. // Walk the nodes inside the block
  13459. walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
  13460. while ((node = walker[start ? 'prev' : 'next']())) {
  13461. if (node.nodeType === 3) {
  13462. lastTextNode = node;
  13463. pos = findSpace(node);
  13464. if (pos !== -1) {
  13465. return {container: node, offset: pos};
  13466. }
  13467. } else if (isBlock(node)) {
  13468. break;
  13469. }
  13470. }
  13471. if (lastTextNode) {
  13472. if (start) {
  13473. offset = 0;
  13474. } else {
  13475. offset = lastTextNode.length;
  13476. }
  13477. return {container: lastTextNode, offset: offset};
  13478. }
  13479. }
  13480. function findSelectorEndPoint(container, sibling_name) {
  13481. var parents, i, y, curFormat;
  13482. if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
  13483. container = container[sibling_name];
  13484. }
  13485. parents = getParents(container);
  13486. for (i = 0; i < parents.length; i++) {
  13487. for (y = 0; y < format.length; y++) {
  13488. curFormat = format[y];
  13489. // If collapsed state is set then skip formats that doesn't match that
  13490. if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
  13491. continue;
  13492. }
  13493. if (dom.is(parents[i], curFormat.selector)) {
  13494. return parents[i];
  13495. }
  13496. }
  13497. }
  13498. return container;
  13499. }
  13500. function findBlockEndPoint(container, sibling_name) {
  13501. var node, root = dom.getRoot();
  13502. // Expand to block of similar type
  13503. if (!format[0].wrapper) {
  13504. node = dom.getParent(container, format[0].block, root);
  13505. }
  13506. // Expand to first wrappable block element or any block element
  13507. if (!node) {
  13508. node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
  13509. // Fixes #6183 where it would expand to editable parent element in inline mode
  13510. return node != root && isTextBlock(node);
  13511. });
  13512. }
  13513. // Exclude inner lists from wrapping
  13514. if (node && format[0].wrapper) {
  13515. node = getParents(node, 'ul,ol').reverse()[0] || node;
  13516. }
  13517. // Didn't find a block element look for first/last wrappable element
  13518. if (!node) {
  13519. node = container;
  13520. while (node[sibling_name] && !isBlock(node[sibling_name])) {
  13521. node = node[sibling_name];
  13522. // Break on BR but include it will be removed later on
  13523. // we can't remove it now since we need to check if it can be wrapped
  13524. if (isEq(node, 'br')) {
  13525. break;
  13526. }
  13527. }
  13528. }
  13529. return node || container;
  13530. }
  13531. // Expand to closest contentEditable element
  13532. startContainer = findParentContentEditable(startContainer);
  13533. endContainer = findParentContentEditable(endContainer);
  13534. // Exclude bookmark nodes if possible
  13535. if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
  13536. startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
  13537. startContainer = startContainer.nextSibling || startContainer;
  13538. if (startContainer.nodeType == 3) {
  13539. startOffset = 0;
  13540. }
  13541. }
  13542. if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
  13543. endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
  13544. endContainer = endContainer.previousSibling || endContainer;
  13545. if (endContainer.nodeType == 3) {
  13546. endOffset = endContainer.length;
  13547. }
  13548. }
  13549. if (format[0].inline) {
  13550. if (rng.collapsed) {
  13551. // Expand left to closest word boundary
  13552. endPoint = findWordEndPoint(startContainer, startOffset, true);
  13553. if (endPoint) {
  13554. startContainer = endPoint.container;
  13555. startOffset = endPoint.offset;
  13556. }
  13557. // Expand right to closest word boundary
  13558. endPoint = findWordEndPoint(endContainer, endOffset);
  13559. if (endPoint) {
  13560. endContainer = endPoint.container;
  13561. endOffset = endPoint.offset;
  13562. }
  13563. }
  13564. // Avoid applying formatting to a trailing space.
  13565. leaf = findLeaf(endContainer, endOffset);
  13566. if (leaf.node) {
  13567. while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
  13568. leaf = findLeaf(leaf.node.previousSibling);
  13569. }
  13570. if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
  13571. leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
  13572. if (leaf.offset > 1) {
  13573. endContainer = leaf.node;
  13574. endContainer.splitText(leaf.offset - 1);
  13575. }
  13576. }
  13577. }
  13578. }
  13579. // Move start/end point up the tree if the leaves are sharp and if we are in different containers
  13580. // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
  13581. // This will reduce the number of wrapper elements that needs to be created
  13582. // Move start point up the tree
  13583. if (format[0].inline || format[0].block_expand) {
  13584. if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
  13585. startContainer = findParentContainer(true);
  13586. }
  13587. if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
  13588. endContainer = findParentContainer();
  13589. }
  13590. }
  13591. // Expand start/end container to matching selector
  13592. if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
  13593. // Find new startContainer/endContainer if there is better one
  13594. startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
  13595. endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
  13596. }
  13597. // Expand start/end container to matching block element or text node
  13598. if (format[0].block || format[0].selector) {
  13599. // Find new startContainer/endContainer if there is better one
  13600. startContainer = findBlockEndPoint(startContainer, 'previousSibling');
  13601. endContainer = findBlockEndPoint(endContainer, 'nextSibling');
  13602. // Non block element then try to expand up the leaf
  13603. if (format[0].block) {
  13604. if (!isBlock(startContainer)) {
  13605. startContainer = findParentContainer(true);
  13606. }
  13607. if (!isBlock(endContainer)) {
  13608. endContainer = findParentContainer();
  13609. }
  13610. }
  13611. }
  13612. // Setup index for startContainer
  13613. if (startContainer.nodeType == 1) {
  13614. startOffset = nodeIndex(startContainer);
  13615. startContainer = startContainer.parentNode;
  13616. }
  13617. // Setup index for endContainer
  13618. if (endContainer.nodeType == 1) {
  13619. endOffset = nodeIndex(endContainer) + 1;
  13620. endContainer = endContainer.parentNode;
  13621. }
  13622. // Return new range like object
  13623. return {
  13624. startContainer: startContainer,
  13625. startOffset: startOffset,
  13626. endContainer: endContainer,
  13627. endOffset: endOffset
  13628. };
  13629. }
  13630. /**
  13631. * Removes the specified format for the specified node. It will also remove the node if it doesn't have
  13632. * any attributes if the format specifies it to do so.
  13633. *
  13634. * @private
  13635. * @param {Object} format Format object with items to remove from node.
  13636. * @param {Object} vars Name/value object with variables to apply to format.
  13637. * @param {Node} node Node to remove the format styles on.
  13638. * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
  13639. * @return {Boolean} True/false if the node was removed or not.
  13640. */
  13641. function removeFormat(format, vars, node, compare_node) {
  13642. var i, attrs, stylesModified;
  13643. // Check if node matches format
  13644. if (!matchName(node, format)) {
  13645. return FALSE;
  13646. }
  13647. // Should we compare with format attribs and styles
  13648. if (format.remove != 'all') {
  13649. // Remove styles
  13650. each(format.styles, function(value, name) {
  13651. value = normalizeStyleValue(replaceVars(value, vars), name);
  13652. // Indexed array
  13653. if (typeof(name) === 'number') {
  13654. name = value;
  13655. compare_node = 0;
  13656. }
  13657. if (!compare_node || isEq(getStyle(compare_node, name), value)) {
  13658. dom.setStyle(node, name, '');
  13659. }
  13660. stylesModified = 1;
  13661. });
  13662. // Remove style attribute if it's empty
  13663. if (stylesModified && dom.getAttrib(node, 'style') === '') {
  13664. node.removeAttribute('style');
  13665. node.removeAttribute('data-mce-style');
  13666. }
  13667. // Remove attributes
  13668. each(format.attributes, function(value, name) {
  13669. var valueOut;
  13670. value = replaceVars(value, vars);
  13671. // Indexed array
  13672. if (typeof(name) === 'number') {
  13673. name = value;
  13674. compare_node = 0;
  13675. }
  13676. if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
  13677. // Keep internal classes
  13678. if (name == 'class') {
  13679. value = dom.getAttrib(node, name);
  13680. if (value) {
  13681. // Build new class value where everything is removed except the internal prefixed classes
  13682. valueOut = '';
  13683. each(value.split(/\s+/), function(cls) {
  13684. if (/mce\w+/.test(cls)) {
  13685. valueOut += (valueOut ? ' ' : '') + cls;
  13686. }
  13687. });
  13688. // We got some internal classes left
  13689. if (valueOut) {
  13690. dom.setAttrib(node, name, valueOut);
  13691. return;
  13692. }
  13693. }
  13694. }
  13695. // IE6 has a bug where the attribute doesn't get removed correctly
  13696. if (name == "class") {
  13697. node.removeAttribute('className');
  13698. }
  13699. // Remove mce prefixed attributes
  13700. if (MCE_ATTR_RE.test(name)) {
  13701. node.removeAttribute('data-mce-' + name);
  13702. }
  13703. node.removeAttribute(name);
  13704. }
  13705. });
  13706. // Remove classes
  13707. each(format.classes, function(value) {
  13708. value = replaceVars(value, vars);
  13709. if (!compare_node || dom.hasClass(compare_node, value)) {
  13710. dom.removeClass(node, value);
  13711. }
  13712. });
  13713. // Check for non internal attributes
  13714. attrs = dom.getAttribs(node);
  13715. for (i = 0; i < attrs.length; i++) {
  13716. if (attrs[i].nodeName.indexOf('_') !== 0) {
  13717. return FALSE;
  13718. }
  13719. }
  13720. }
  13721. // Remove the inline child if it's empty for example <b> or <span>
  13722. if (format.remove != 'none') {
  13723. removeNode(node, format);
  13724. return TRUE;
  13725. }
  13726. }
  13727. /**
  13728. * Removes the node and wrap it's children in paragraphs before doing so or
  13729. * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
  13730. *
  13731. * If the div in the node below gets removed:
  13732. * text<div>text</div>text
  13733. *
  13734. * Output becomes:
  13735. * text<div><br />text<br /></div>text
  13736. *
  13737. * So when the div is removed the result is:
  13738. * text<br />text<br />text
  13739. *
  13740. * @private
  13741. * @param {Node} node Node to remove + apply BR/P elements to.
  13742. * @param {Object} format Format rule.
  13743. * @return {Node} Input node.
  13744. */
  13745. function removeNode(node, format) {
  13746. var parentNode = node.parentNode, rootBlockElm;
  13747. function find(node, next, inc) {
  13748. node = getNonWhiteSpaceSibling(node, next, inc);
  13749. return !node || (node.nodeName == 'BR' || isBlock(node));
  13750. }
  13751. if (format.block) {
  13752. if (!forcedRootBlock) {
  13753. // Append BR elements if needed before we remove the block
  13754. if (isBlock(node) && !isBlock(parentNode)) {
  13755. if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
  13756. node.insertBefore(dom.create('br'), node.firstChild);
  13757. }
  13758. if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
  13759. node.appendChild(dom.create('br'));
  13760. }
  13761. }
  13762. } else {
  13763. // Wrap the block in a forcedRootBlock if we are at the root of document
  13764. if (parentNode == dom.getRoot()) {
  13765. if (!format.list_block || !isEq(node, format.list_block)) {
  13766. each(grep(node.childNodes), function(node) {
  13767. if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
  13768. if (!rootBlockElm) {
  13769. rootBlockElm = wrap(node, forcedRootBlock);
  13770. dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
  13771. } else {
  13772. rootBlockElm.appendChild(node);
  13773. }
  13774. } else {
  13775. rootBlockElm = 0;
  13776. }
  13777. });
  13778. }
  13779. }
  13780. }
  13781. }
  13782. // Never remove nodes that isn't the specified inline element if a selector is specified too
  13783. if (format.selector && format.inline && !isEq(format.inline, node)) {
  13784. return;
  13785. }
  13786. dom.remove(node, 1);
  13787. }
  13788. /**
  13789. * Returns the next/previous non whitespace node.
  13790. *
  13791. * @private
  13792. * @param {Node} node Node to start at.
  13793. * @param {boolean} next (Optional) Include next or previous node defaults to previous.
  13794. * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
  13795. * @return {Node} Next or previous node or undefined if it wasn't found.
  13796. */
  13797. function getNonWhiteSpaceSibling(node, next, inc) {
  13798. if (node) {
  13799. next = next ? 'nextSibling' : 'previousSibling';
  13800. for (node = inc ? node : node[next]; node; node = node[next]) {
  13801. if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
  13802. return node;
  13803. }
  13804. }
  13805. }
  13806. }
  13807. /**
  13808. * Checks if the specified node is a bookmark node or not.
  13809. *
  13810. * @private
  13811. * @param {Node} node Node to check if it's a bookmark node or not.
  13812. * @return {Boolean} true/false if the node is a bookmark node.
  13813. */
  13814. function isBookmarkNode(node) {
  13815. return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
  13816. }
  13817. /**
  13818. * Merges the next/previous sibling element if they match.
  13819. *
  13820. * @private
  13821. * @param {Node} prev Previous node to compare/merge.
  13822. * @param {Node} next Next node to compare/merge.
  13823. * @return {Node} Next node if we didn't merge and prev node if we did.
  13824. */
  13825. function mergeSiblings(prev, next) {
  13826. var sibling, tmpSibling;
  13827. /**
  13828. * Compares two nodes and checks if it's attributes and styles matches.
  13829. * This doesn't compare classes as items since their order is significant.
  13830. *
  13831. * @private
  13832. * @param {Node} node1 First node to compare with.
  13833. * @param {Node} node2 Second node to compare with.
  13834. * @return {boolean} True/false if the nodes are the same or not.
  13835. */
  13836. function compareElements(node1, node2) {
  13837. // Not the same name
  13838. if (node1.nodeName != node2.nodeName) {
  13839. return FALSE;
  13840. }
  13841. /**
  13842. * Returns all the nodes attributes excluding internal ones, styles and classes.
  13843. *
  13844. * @private
  13845. * @param {Node} node Node to get attributes from.
  13846. * @return {Object} Name/value object with attributes and attribute values.
  13847. */
  13848. function getAttribs(node) {
  13849. var attribs = {};
  13850. each(dom.getAttribs(node), function(attr) {
  13851. var name = attr.nodeName.toLowerCase();
  13852. // Don't compare internal attributes or style
  13853. if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style') {
  13854. attribs[name] = dom.getAttrib(node, name);
  13855. }
  13856. });
  13857. return attribs;
  13858. }
  13859. /**
  13860. * Compares two objects checks if it's key + value exists in the other one.
  13861. *
  13862. * @private
  13863. * @param {Object} obj1 First object to compare.
  13864. * @param {Object} obj2 Second object to compare.
  13865. * @return {boolean} True/false if the objects matches or not.
  13866. */
  13867. function compareObjects(obj1, obj2) {
  13868. var value, name;
  13869. for (name in obj1) {
  13870. // Obj1 has item obj2 doesn't have
  13871. if (obj1.hasOwnProperty(name)) {
  13872. value = obj2[name];
  13873. // Obj2 doesn't have obj1 item
  13874. if (value === undef) {
  13875. return FALSE;
  13876. }
  13877. // Obj2 item has a different value
  13878. if (obj1[name] != value) {
  13879. return FALSE;
  13880. }
  13881. // Delete similar value
  13882. delete obj2[name];
  13883. }
  13884. }
  13885. // Check if obj 2 has something obj 1 doesn't have
  13886. for (name in obj2) {
  13887. // Obj2 has item obj1 doesn't have
  13888. if (obj2.hasOwnProperty(name)) {
  13889. return FALSE;
  13890. }
  13891. }
  13892. return TRUE;
  13893. }
  13894. // Attribs are not the same
  13895. if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
  13896. return FALSE;
  13897. }
  13898. // Styles are not the same
  13899. if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
  13900. return FALSE;
  13901. }
  13902. return !isBookmarkNode(node1) && !isBookmarkNode(node2);
  13903. }
  13904. function findElementSibling(node, sibling_name) {
  13905. for (sibling = node; sibling; sibling = sibling[sibling_name]) {
  13906. if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
  13907. return node;
  13908. }
  13909. if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
  13910. return sibling;
  13911. }
  13912. }
  13913. return node;
  13914. }
  13915. // Check if next/prev exists and that they are elements
  13916. if (prev && next) {
  13917. // If previous sibling is empty then jump over it
  13918. prev = findElementSibling(prev, 'previousSibling');
  13919. next = findElementSibling(next, 'nextSibling');
  13920. // Compare next and previous nodes
  13921. if (compareElements(prev, next)) {
  13922. // Append nodes between
  13923. for (sibling = prev.nextSibling; sibling && sibling != next;) {
  13924. tmpSibling = sibling;
  13925. sibling = sibling.nextSibling;
  13926. prev.appendChild(tmpSibling);
  13927. }
  13928. // Remove next node
  13929. dom.remove(next);
  13930. // Move children into prev node
  13931. each(grep(next.childNodes), function(node) {
  13932. prev.appendChild(node);
  13933. });
  13934. return prev;
  13935. }
  13936. }
  13937. return next;
  13938. }
  13939. function getContainer(rng, start) {
  13940. var container, offset, lastIdx;
  13941. container = rng[start ? 'startContainer' : 'endContainer'];
  13942. offset = rng[start ? 'startOffset' : 'endOffset'];
  13943. if (container.nodeType == 1) {
  13944. lastIdx = container.childNodes.length - 1;
  13945. if (!start && offset) {
  13946. offset--;
  13947. }
  13948. container = container.childNodes[offset > lastIdx ? lastIdx : offset];
  13949. }
  13950. // If start text node is excluded then walk to the next node
  13951. if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
  13952. container = new TreeWalker(container, ed.getBody()).next() || container;
  13953. }
  13954. // If end text node is excluded then walk to the previous node
  13955. if (container.nodeType === 3 && !start && offset === 0) {
  13956. container = new TreeWalker(container, ed.getBody()).prev() || container;
  13957. }
  13958. return container;
  13959. }
  13960. function performCaretAction(type, name, vars) {
  13961. var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
  13962. // Creates a caret container bogus element
  13963. function createCaretContainer(fill) {
  13964. var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
  13965. if (fill) {
  13966. caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
  13967. }
  13968. return caretContainer;
  13969. }
  13970. function isCaretContainerEmpty(node, nodes) {
  13971. while (node) {
  13972. if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
  13973. return false;
  13974. }
  13975. // Collect nodes
  13976. if (nodes && node.nodeType === 1) {
  13977. nodes.push(node);
  13978. }
  13979. node = node.firstChild;
  13980. }
  13981. return true;
  13982. }
  13983. // Returns any parent caret container element
  13984. function getParentCaretContainer(node) {
  13985. while (node) {
  13986. if (node.id === caretContainerId) {
  13987. return node;
  13988. }
  13989. node = node.parentNode;
  13990. }
  13991. }
  13992. // Finds the first text node in the specified node
  13993. function findFirstTextNode(node) {
  13994. var walker;
  13995. if (node) {
  13996. walker = new TreeWalker(node, node);
  13997. for (node = walker.current(); node; node = walker.next()) {
  13998. if (node.nodeType === 3) {
  13999. return node;
  14000. }
  14001. }
  14002. }
  14003. }
  14004. // Removes the caret container for the specified node or all on the current document
  14005. function removeCaretContainer(node, move_caret) {
  14006. var child, rng;
  14007. if (!node) {
  14008. node = getParentCaretContainer(selection.getStart());
  14009. if (!node) {
  14010. while ((node = dom.get(caretContainerId))) {
  14011. removeCaretContainer(node, false);
  14012. }
  14013. }
  14014. } else {
  14015. rng = selection.getRng(true);
  14016. if (isCaretContainerEmpty(node)) {
  14017. if (move_caret !== false) {
  14018. rng.setStartBefore(node);
  14019. rng.setEndBefore(node);
  14020. }
  14021. dom.remove(node);
  14022. } else {
  14023. child = findFirstTextNode(node);
  14024. if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
  14025. child = child.deleteData(0, 1);
  14026. }
  14027. dom.remove(node, 1);
  14028. }
  14029. selection.setRng(rng);
  14030. }
  14031. }
  14032. // Applies formatting to the caret postion
  14033. function applyCaretFormat() {
  14034. var rng, caretContainer, textNode, offset, bookmark, container, text;
  14035. rng = selection.getRng(true);
  14036. offset = rng.startOffset;
  14037. container = rng.startContainer;
  14038. text = container.nodeValue;
  14039. caretContainer = getParentCaretContainer(selection.getStart());
  14040. if (caretContainer) {
  14041. textNode = findFirstTextNode(caretContainer);
  14042. }
  14043. // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
  14044. if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
  14045. // Get bookmark of caret position
  14046. bookmark = selection.getBookmark();
  14047. // Collapse bookmark range (WebKit)
  14048. rng.collapse(true);
  14049. // Expand the range to the closest word and split it at those points
  14050. rng = expandRng(rng, get(name));
  14051. rng = rangeUtils.split(rng);
  14052. // Apply the format to the range
  14053. apply(name, vars, rng);
  14054. // Move selection back to caret position
  14055. selection.moveToBookmark(bookmark);
  14056. } else {
  14057. if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
  14058. caretContainer = createCaretContainer(true);
  14059. textNode = caretContainer.firstChild;
  14060. rng.insertNode(caretContainer);
  14061. offset = 1;
  14062. apply(name, vars, caretContainer);
  14063. } else {
  14064. apply(name, vars, caretContainer);
  14065. }
  14066. // Move selection to text node
  14067. selection.setCursorLocation(textNode, offset);
  14068. }
  14069. }
  14070. function removeCaretFormat() {
  14071. var rng = selection.getRng(true), container, offset, bookmark,
  14072. hasContentAfter, node, formatNode, parents = [], i, caretContainer;
  14073. container = rng.startContainer;
  14074. offset = rng.startOffset;
  14075. node = container;
  14076. if (container.nodeType == 3) {
  14077. if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
  14078. hasContentAfter = true;
  14079. }
  14080. node = node.parentNode;
  14081. }
  14082. while (node) {
  14083. if (matchNode(node, name, vars)) {
  14084. formatNode = node;
  14085. break;
  14086. }
  14087. if (node.nextSibling) {
  14088. hasContentAfter = true;
  14089. }
  14090. parents.push(node);
  14091. node = node.parentNode;
  14092. }
  14093. // Node doesn't have the specified format
  14094. if (!formatNode) {
  14095. return;
  14096. }
  14097. // Is there contents after the caret then remove the format on the element
  14098. if (hasContentAfter) {
  14099. // Get bookmark of caret position
  14100. bookmark = selection.getBookmark();
  14101. // Collapse bookmark range (WebKit)
  14102. rng.collapse(true);
  14103. // Expand the range to the closest word and split it at those points
  14104. rng = expandRng(rng, get(name), true);
  14105. rng = rangeUtils.split(rng);
  14106. // Remove the format from the range
  14107. remove(name, vars, rng);
  14108. // Move selection back to caret position
  14109. selection.moveToBookmark(bookmark);
  14110. } else {
  14111. caretContainer = createCaretContainer();
  14112. node = caretContainer;
  14113. for (i = parents.length - 1; i >= 0; i--) {
  14114. node.appendChild(dom.clone(parents[i], false));
  14115. node = node.firstChild;
  14116. }
  14117. // Insert invisible character into inner most format element
  14118. node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
  14119. node = node.firstChild;
  14120. var block = dom.getParent(formatNode, isTextBlock);
  14121. if (block && dom.isEmpty(block)) {
  14122. // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
  14123. formatNode.parentNode.replaceChild(caretContainer, formatNode);
  14124. } else {
  14125. // Insert caret container after the formated node
  14126. dom.insertAfter(caretContainer, formatNode);
  14127. }
  14128. // Move selection to text node
  14129. selection.setCursorLocation(node, 1);
  14130. // If the formatNode is empty, we can remove it safely.
  14131. if (dom.isEmpty(formatNode)) {
  14132. dom.remove(formatNode);
  14133. }
  14134. }
  14135. }
  14136. // Checks if the parent caret container node isn't empty if that is the case it
  14137. // will remove the bogus state on all children that isn't empty
  14138. function unmarkBogusCaretParents() {
  14139. var caretContainer;
  14140. caretContainer = getParentCaretContainer(selection.getStart());
  14141. if (caretContainer && !dom.isEmpty(caretContainer)) {
  14142. walk(caretContainer, function(node) {
  14143. if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
  14144. dom.setAttrib(node, 'data-mce-bogus', null);
  14145. }
  14146. }, 'childNodes');
  14147. }
  14148. }
  14149. // Only bind the caret events once
  14150. if (!ed._hasCaretEvents) {
  14151. // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
  14152. markCaretContainersBogus = function() {
  14153. var nodes = [], i;
  14154. if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
  14155. // Mark children
  14156. i = nodes.length;
  14157. while (i--) {
  14158. dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
  14159. }
  14160. }
  14161. };
  14162. disableCaretContainer = function(e) {
  14163. var keyCode = e.keyCode;
  14164. removeCaretContainer();
  14165. // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
  14166. if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
  14167. removeCaretContainer(getParentCaretContainer(selection.getStart()));
  14168. }
  14169. unmarkBogusCaretParents();
  14170. };
  14171. // Remove bogus state if they got filled by contents using editor.selection.setContent
  14172. ed.on('SetContent', function(e) {
  14173. if (e.selection) {
  14174. unmarkBogusCaretParents();
  14175. }
  14176. });
  14177. ed._hasCaretEvents = true;
  14178. }
  14179. // Do apply or remove caret format
  14180. if (type == "apply") {
  14181. applyCaretFormat();
  14182. } else {
  14183. removeCaretFormat();
  14184. }
  14185. }
  14186. /**
  14187. * Moves the start to the first suitable text node.
  14188. */
  14189. function moveStart(rng) {
  14190. var container = rng.startContainer,
  14191. offset = rng.startOffset, isAtEndOfText,
  14192. walker, node, nodes, tmpNode;
  14193. // Convert text node into index if possible
  14194. if (container.nodeType == 3 && offset >= container.nodeValue.length) {
  14195. // Get the parent container location and walk from there
  14196. offset = nodeIndex(container);
  14197. container = container.parentNode;
  14198. isAtEndOfText = true;
  14199. }
  14200. // Move startContainer/startOffset in to a suitable node
  14201. if (container.nodeType == 1) {
  14202. nodes = container.childNodes;
  14203. container = nodes[Math.min(offset, nodes.length - 1)];
  14204. walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
  14205. // If offset is at end of the parent node walk to the next one
  14206. if (offset > nodes.length - 1 || isAtEndOfText) {
  14207. walker.next();
  14208. }
  14209. for (node = walker.current(); node; node = walker.next()) {
  14210. if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
  14211. // IE has a "neat" feature where it moves the start node into the closest element
  14212. // we can avoid this by inserting an element before it and then remove it after we set the selection
  14213. tmpNode = dom.create('a', null, INVISIBLE_CHAR);
  14214. node.parentNode.insertBefore(tmpNode, node);
  14215. // Set selection and remove tmpNode
  14216. rng.setStart(node, 0);
  14217. selection.setRng(rng);
  14218. dom.remove(tmpNode);
  14219. return;
  14220. }
  14221. }
  14222. }
  14223. }
  14224. };
  14225. });
  14226. // Included from: js/tinymce/classes/UndoManager.js
  14227. /**
  14228. * UndoManager.js
  14229. *
  14230. * Copyright, Moxiecode Systems AB
  14231. * Released under LGPL License.
  14232. *
  14233. * License: http://www.tinymce.com/license
  14234. * Contributing: http://www.tinymce.com/contributing
  14235. */
  14236. /**
  14237. * 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.
  14238. *
  14239. * @class tinymce.UndoManager
  14240. */
  14241. define("tinymce/UndoManager", [
  14242. "tinymce/Env",
  14243. "tinymce/util/Tools"
  14244. ], function(Env, Tools) {
  14245. var trim = Tools.trim, trimContentRegExp;
  14246. trimContentRegExp = new RegExp([
  14247. '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
  14248. '<div[^>]+data-mce-bogus[^>]+><\\/div>', // Trim bogus divs like resize handles
  14249. '\\s?data-mce-selected="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
  14250. ].join('|'), 'gi');
  14251. return function(editor) {
  14252. var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
  14253. // Returns a trimmed version of the current editor contents
  14254. function getContent() {
  14255. return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
  14256. }
  14257. function addNonTypingUndoLevel(e) {
  14258. self.typing = false;
  14259. self.add({}, e);
  14260. }
  14261. // Add initial undo level when the editor is initialized
  14262. editor.on('init', function() {
  14263. self.add();
  14264. });
  14265. // Get position before an execCommand is processed
  14266. editor.on('BeforeExecCommand', function(e) {
  14267. var cmd = e.command;
  14268. if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  14269. self.beforeChange();
  14270. }
  14271. });
  14272. // Add undo level after an execCommand call was made
  14273. editor.on('ExecCommand', function(e) {
  14274. var cmd = e.command;
  14275. if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
  14276. addNonTypingUndoLevel(e);
  14277. }
  14278. });
  14279. editor.on('ObjectResizeStart', function() {
  14280. self.beforeChange();
  14281. });
  14282. editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
  14283. editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
  14284. editor.on('KeyUp', function(e) {
  14285. var keyCode = e.keyCode;
  14286. if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
  14287. addNonTypingUndoLevel();
  14288. editor.nodeChanged();
  14289. }
  14290. if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
  14291. editor.nodeChanged();
  14292. }
  14293. // Fire a TypingUndo event on the first character entered
  14294. if (isFirstTypedCharacter && self.typing) {
  14295. // Make the it dirty if the content was changed after typing the first character
  14296. if (!editor.isDirty()) {
  14297. editor.isNotDirty = !data[0] || getContent() == data[0].content;
  14298. // Fire initial change event
  14299. if (!editor.isNotDirty) {
  14300. editor.fire('change', {level: data[0], lastLevel: null});
  14301. }
  14302. }
  14303. editor.fire('TypingUndo');
  14304. isFirstTypedCharacter = false;
  14305. editor.nodeChanged();
  14306. }
  14307. });
  14308. editor.on('KeyDown', function(e) {
  14309. var keyCode = e.keyCode;
  14310. // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
  14311. if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
  14312. if (self.typing) {
  14313. addNonTypingUndoLevel(e);
  14314. }
  14315. return;
  14316. }
  14317. // If key isn't shift,ctrl,alt,capslock,metakey
  14318. if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
  14319. self.beforeChange();
  14320. self.typing = true;
  14321. self.add({}, e);
  14322. isFirstTypedCharacter = true;
  14323. }
  14324. });
  14325. editor.on('MouseDown', function(e) {
  14326. if (self.typing) {
  14327. addNonTypingUndoLevel(e);
  14328. }
  14329. });
  14330. // Add keyboard shortcuts for undo/redo keys
  14331. editor.addShortcut('ctrl+z', '', 'Undo');
  14332. editor.addShortcut('ctrl+y,ctrl+shift+z', '', 'Redo');
  14333. editor.on('AddUndo Undo Redo ClearUndos MouseUp', function(e) {
  14334. if (!e.isDefaultPrevented()) {
  14335. editor.nodeChanged();
  14336. }
  14337. });
  14338. self = {
  14339. // Explose for debugging reasons
  14340. data: data,
  14341. /**
  14342. * State if the user is currently typing or not. This will add a typing operation into one undo
  14343. * level instead of one new level for each keystroke.
  14344. *
  14345. * @field {Boolean} typing
  14346. */
  14347. typing: false,
  14348. /**
  14349. * Stores away a bookmark to be used when performing an undo action so that the selection is before
  14350. * the change has been made.
  14351. *
  14352. * @method beforeChange
  14353. */
  14354. beforeChange: function() {
  14355. if (!locks) {
  14356. beforeBookmark = editor.selection.getBookmark(2, true);
  14357. }
  14358. },
  14359. /**
  14360. * Adds a new undo level/snapshot to the undo list.
  14361. *
  14362. * @method add
  14363. * @param {Object} level Optional undo level object to add.
  14364. * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
  14365. * @return {Object} Undo level that got added or null it a level wasn't needed.
  14366. */
  14367. add: function(level, event) {
  14368. var i, settings = editor.settings, lastLevel;
  14369. level = level || {};
  14370. level.content = getContent();
  14371. if (locks || editor.removed) {
  14372. return null;
  14373. }
  14374. if (editor.fire('BeforeAddUndo', {level: level, originalEvent: event}).isDefaultPrevented()) {
  14375. return null;
  14376. }
  14377. // Add undo level if needed
  14378. lastLevel = data[index];
  14379. if (lastLevel && lastLevel.content == level.content) {
  14380. return null;
  14381. }
  14382. // Set before bookmark on previous level
  14383. if (data[index]) {
  14384. data[index].beforeBookmark = beforeBookmark;
  14385. }
  14386. // Time to compress
  14387. if (settings.custom_undo_redo_levels) {
  14388. if (data.length > settings.custom_undo_redo_levels) {
  14389. for (i = 0; i < data.length - 1; i++) {
  14390. data[i] = data[i + 1];
  14391. }
  14392. data.length--;
  14393. index = data.length;
  14394. }
  14395. }
  14396. // Get a non intrusive normalized bookmark
  14397. level.bookmark = editor.selection.getBookmark(2, true);
  14398. // Crop array if needed
  14399. if (index < data.length - 1) {
  14400. data.length = index + 1;
  14401. }
  14402. data.push(level);
  14403. index = data.length - 1;
  14404. var args = {level: level, lastLevel: lastLevel, originalEvent: event};
  14405. editor.fire('AddUndo', args);
  14406. if (index > 0) {
  14407. editor.isNotDirty = false;
  14408. editor.fire('change', args);
  14409. }
  14410. return level;
  14411. },
  14412. /**
  14413. * Undoes the last action.
  14414. *
  14415. * @method undo
  14416. * @return {Object} Undo level or null if no undo was performed.
  14417. */
  14418. undo: function() {
  14419. var level;
  14420. if (self.typing) {
  14421. self.add();
  14422. self.typing = false;
  14423. }
  14424. if (index > 0) {
  14425. level = data[--index];
  14426. // Undo to first index then set dirty state to false
  14427. if (index === 0) {
  14428. editor.isNotDirty = true;
  14429. }
  14430. editor.setContent(level.content, {format: 'raw'});
  14431. editor.selection.moveToBookmark(level.beforeBookmark);
  14432. editor.fire('undo', {level: level});
  14433. }
  14434. return level;
  14435. },
  14436. /**
  14437. * Redoes the last action.
  14438. *
  14439. * @method redo
  14440. * @return {Object} Redo level or null if no redo was performed.
  14441. */
  14442. redo: function() {
  14443. var level;
  14444. if (index < data.length - 1) {
  14445. level = data[++index];
  14446. editor.setContent(level.content, {format: 'raw'});
  14447. editor.selection.moveToBookmark(level.bookmark);
  14448. editor.fire('redo', {level: level});
  14449. }
  14450. return level;
  14451. },
  14452. /**
  14453. * Removes all undo levels.
  14454. *
  14455. * @method clear
  14456. */
  14457. clear: function() {
  14458. data = [];
  14459. index = 0;
  14460. self.typing = false;
  14461. editor.fire('ClearUndos');
  14462. },
  14463. /**
  14464. * Returns true/false if the undo manager has any undo levels.
  14465. *
  14466. * @method hasUndo
  14467. * @return {Boolean} true/false if the undo manager has any undo levels.
  14468. */
  14469. hasUndo: function() {
  14470. // Has undo levels or typing and content isn't the same as the initial level
  14471. return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
  14472. },
  14473. /**
  14474. * Returns true/false if the undo manager has any redo levels.
  14475. *
  14476. * @method hasRedo
  14477. * @return {Boolean} true/false if the undo manager has any redo levels.
  14478. */
  14479. hasRedo: function() {
  14480. return index < data.length - 1 && !this.typing;
  14481. },
  14482. /**
  14483. * Executes the specified function in an undo transation. The selection
  14484. * before the modification will be stored to the undo stack and if the DOM changes
  14485. * it will add a new undo level. Any methods within the transation that adds undo levels will
  14486. * be ignored. So a transation can include calls to execCommand or editor.insertContent.
  14487. *
  14488. * @method transact
  14489. * @param {function} callback Function to execute dom manipulation logic in.
  14490. */
  14491. transact: function(callback) {
  14492. self.beforeChange();
  14493. try {
  14494. locks++;
  14495. callback();
  14496. } finally {
  14497. locks--;
  14498. }
  14499. self.add();
  14500. }
  14501. };
  14502. return self;
  14503. };
  14504. });
  14505. // Included from: js/tinymce/classes/EnterKey.js
  14506. /**
  14507. * EnterKey.js
  14508. *
  14509. * Copyright, Moxiecode Systems AB
  14510. * Released under LGPL License.
  14511. *
  14512. * License: http://www.tinymce.com/license
  14513. * Contributing: http://www.tinymce.com/contributing
  14514. */
  14515. /**
  14516. * Contains logic for handling the enter key to split/generate block elements.
  14517. */
  14518. define("tinymce/EnterKey", [
  14519. "tinymce/dom/TreeWalker",
  14520. "tinymce/dom/RangeUtils",
  14521. "tinymce/Env"
  14522. ], function(TreeWalker, RangeUtils, Env) {
  14523. var isIE = Env.ie && Env.ie < 11;
  14524. return function(editor) {
  14525. var dom = editor.dom, selection = editor.selection, settings = editor.settings;
  14526. var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
  14527. function handleEnterKey(evt) {
  14528. var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
  14529. newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
  14530. // Returns true if the block can be split into two blocks or not
  14531. function canSplitBlock(node) {
  14532. return node &&
  14533. dom.isBlock(node) &&
  14534. !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
  14535. !/^(fixed|absolute)/i.test(node.style.position) &&
  14536. dom.getContentEditable(node) !== "true";
  14537. }
  14538. // Renders empty block on IE
  14539. function renderBlockOnIE(block) {
  14540. var oldRng;
  14541. if (dom.isBlock(block)) {
  14542. oldRng = selection.getRng();
  14543. block.appendChild(dom.create('span', null, '\u00a0'));
  14544. selection.select(block);
  14545. block.lastChild.outerHTML = '';
  14546. selection.setRng(oldRng);
  14547. }
  14548. }
  14549. // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
  14550. function trimInlineElementsOnLeftSideOfBlock(block) {
  14551. var node = block, firstChilds = [], i;
  14552. // Find inner most first child ex: <p><i><b>*</b></i></p>
  14553. while ((node = node.firstChild)) {
  14554. if (dom.isBlock(node)) {
  14555. return;
  14556. }
  14557. if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  14558. firstChilds.push(node);
  14559. }
  14560. }
  14561. i = firstChilds.length;
  14562. while (i--) {
  14563. node = firstChilds[i];
  14564. if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
  14565. dom.remove(node);
  14566. } else {
  14567. // Remove <a> </a> see #5381
  14568. if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
  14569. dom.remove(node);
  14570. }
  14571. }
  14572. }
  14573. }
  14574. // Moves the caret to a suitable position within the root for example in the first non
  14575. // pure whitespace text node or before an image
  14576. function moveToCaretPosition(root) {
  14577. var walker, node, rng, lastNode = root, tempElm;
  14578. function firstNonWhiteSpaceNodeSibling(node) {
  14579. while (node) {
  14580. if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
  14581. return node;
  14582. }
  14583. node = node.nextSibling;
  14584. }
  14585. }
  14586. // Old IE versions doesn't properly render blocks with br elements in them
  14587. // For example <p><br></p> wont be rendered correctly in a contentEditable area
  14588. // until you remove the br producing <p></p>
  14589. if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
  14590. if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
  14591. dom.remove(parentBlock.firstChild);
  14592. }
  14593. }
  14594. if (root.nodeName == 'LI') {
  14595. var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
  14596. if (firstChild && /^(UL|OL)$/.test(firstChild.nodeName)) {
  14597. root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
  14598. }
  14599. }
  14600. rng = dom.createRng();
  14601. if (root.hasChildNodes()) {
  14602. walker = new TreeWalker(root, root);
  14603. while ((node = walker.current())) {
  14604. if (node.nodeType == 3) {
  14605. rng.setStart(node, 0);
  14606. rng.setEnd(node, 0);
  14607. break;
  14608. }
  14609. if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
  14610. rng.setStartBefore(node);
  14611. rng.setEndBefore(node);
  14612. break;
  14613. }
  14614. lastNode = node;
  14615. node = walker.next();
  14616. }
  14617. if (!node) {
  14618. rng.setStart(lastNode, 0);
  14619. rng.setEnd(lastNode, 0);
  14620. }
  14621. } else {
  14622. if (root.nodeName == 'BR') {
  14623. if (root.nextSibling && dom.isBlock(root.nextSibling)) {
  14624. // Trick on older IE versions to render the caret before the BR between two lists
  14625. if (!documentMode || documentMode < 9) {
  14626. tempElm = dom.create('br');
  14627. root.parentNode.insertBefore(tempElm, root);
  14628. }
  14629. rng.setStartBefore(root);
  14630. rng.setEndBefore(root);
  14631. } else {
  14632. rng.setStartAfter(root);
  14633. rng.setEndAfter(root);
  14634. }
  14635. } else {
  14636. rng.setStart(root, 0);
  14637. rng.setEnd(root, 0);
  14638. }
  14639. }
  14640. selection.setRng(rng);
  14641. // Remove tempElm created for old IE:s
  14642. dom.remove(tempElm);
  14643. selection.scrollIntoView(root);
  14644. }
  14645. function setForcedBlockAttrs(node) {
  14646. var forcedRootBlockName = settings.forced_root_block;
  14647. if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
  14648. dom.setAttribs(node, settings.forced_root_block_attrs);
  14649. }
  14650. }
  14651. // Creates a new block element by cloning the current one or creating a new one if the name is specified
  14652. // This function will also copy any text formatting from the parent block and add it to the new one
  14653. function createNewBlock(name) {
  14654. var node = container, block, clonedNode, caretNode;
  14655. if (name || parentBlockName == "TABLE") {
  14656. block = dom.create(name || newBlockName);
  14657. setForcedBlockAttrs(block);
  14658. } else {
  14659. block = parentBlock.cloneNode(false);
  14660. }
  14661. caretNode = block;
  14662. // Clone any parent styles
  14663. if (settings.keep_styles !== false) {
  14664. do {
  14665. if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
  14666. // Never clone a caret containers
  14667. if (node.id == '_mce_caret') {
  14668. continue;
  14669. }
  14670. clonedNode = node.cloneNode(false);
  14671. dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
  14672. if (block.hasChildNodes()) {
  14673. clonedNode.appendChild(block.firstChild);
  14674. block.appendChild(clonedNode);
  14675. } else {
  14676. caretNode = clonedNode;
  14677. block.appendChild(clonedNode);
  14678. }
  14679. }
  14680. } while ((node = node.parentNode));
  14681. }
  14682. // BR is needed in empty blocks on non IE browsers
  14683. if (!isIE) {
  14684. caretNode.innerHTML = '<br data-mce-bogus="1">';
  14685. }
  14686. return block;
  14687. }
  14688. // Returns true/false if the caret is at the start/end of the parent block element
  14689. function isCaretAtStartOrEndOfBlock(start) {
  14690. var walker, node, name;
  14691. // Caret is in the middle of a text node like "a|b"
  14692. if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
  14693. return false;
  14694. }
  14695. // If after the last element in block node edge case for #5091
  14696. if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
  14697. return true;
  14698. }
  14699. // If the caret if before the first element in parentBlock
  14700. if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
  14701. return true;
  14702. }
  14703. // Caret can be before/after a table
  14704. if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
  14705. return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
  14706. }
  14707. // Walk the DOM and look for text nodes or non empty elements
  14708. walker = new TreeWalker(container, parentBlock);
  14709. // If caret is in beginning or end of a text block then jump to the next/previous node
  14710. if (container.nodeType == 3) {
  14711. if (start && offset === 0) {
  14712. walker.prev();
  14713. } else if (!start && offset == container.nodeValue.length) {
  14714. walker.next();
  14715. }
  14716. }
  14717. while ((node = walker.current())) {
  14718. if (node.nodeType === 1) {
  14719. // Ignore bogus elements
  14720. if (!node.getAttribute('data-mce-bogus')) {
  14721. // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
  14722. name = node.nodeName.toLowerCase();
  14723. if (nonEmptyElementsMap[name] && name !== 'br') {
  14724. return false;
  14725. }
  14726. }
  14727. } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
  14728. return false;
  14729. }
  14730. if (start) {
  14731. walker.prev();
  14732. } else {
  14733. walker.next();
  14734. }
  14735. }
  14736. return true;
  14737. }
  14738. // Wraps any text nodes or inline elements in the specified forced root block name
  14739. function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
  14740. var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
  14741. // Not in a block element or in a table cell or caption
  14742. parentBlock = dom.getParent(container, dom.isBlock);
  14743. rootBlockName = editor.getBody().nodeName.toLowerCase();
  14744. if (!parentBlock || !canSplitBlock(parentBlock)) {
  14745. parentBlock = parentBlock || editableRoot;
  14746. if (!parentBlock.hasChildNodes()) {
  14747. newBlock = dom.create(blockName);
  14748. setForcedBlockAttrs(newBlock);
  14749. parentBlock.appendChild(newBlock);
  14750. rng.setStart(newBlock, 0);
  14751. rng.setEnd(newBlock, 0);
  14752. return newBlock;
  14753. }
  14754. // Find parent that is the first child of parentBlock
  14755. node = container;
  14756. while (node.parentNode != parentBlock) {
  14757. node = node.parentNode;
  14758. }
  14759. // Loop left to find start node start wrapping at
  14760. while (node && !dom.isBlock(node)) {
  14761. startNode = node;
  14762. node = node.previousSibling;
  14763. }
  14764. if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
  14765. newBlock = dom.create(blockName);
  14766. setForcedBlockAttrs(newBlock);
  14767. startNode.parentNode.insertBefore(newBlock, startNode);
  14768. // Start wrapping until we hit a block
  14769. node = startNode;
  14770. while (node && !dom.isBlock(node)) {
  14771. next = node.nextSibling;
  14772. newBlock.appendChild(node);
  14773. node = next;
  14774. }
  14775. // Restore range to it's past location
  14776. rng.setStart(container, offset);
  14777. rng.setEnd(container, offset);
  14778. }
  14779. }
  14780. return container;
  14781. }
  14782. // Inserts a block or br before/after or in the middle of a split list of the LI is empty
  14783. function handleEmptyListItem() {
  14784. function isFirstOrLastLi(first) {
  14785. var node = containerBlock[first ? 'firstChild' : 'lastChild'];
  14786. // Find first/last element since there might be whitespace there
  14787. while (node) {
  14788. if (node.nodeType == 1) {
  14789. break;
  14790. }
  14791. node = node[first ? 'nextSibling' : 'previousSibling'];
  14792. }
  14793. return node === parentBlock;
  14794. }
  14795. function getContainerBlock() {
  14796. var containerBlockParent = containerBlock.parentNode;
  14797. if (containerBlockParent.nodeName == 'LI') {
  14798. return containerBlockParent;
  14799. }
  14800. return containerBlock;
  14801. }
  14802. // Check if we are in an nested list
  14803. var containerBlockParentName = containerBlock.parentNode.nodeName;
  14804. if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
  14805. newBlockName = 'LI';
  14806. }
  14807. newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
  14808. if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
  14809. if (containerBlockParentName == 'LI') {
  14810. // Nested list is inside a LI
  14811. dom.insertAfter(newBlock, getContainerBlock());
  14812. } else {
  14813. // Is first and last list item then replace the OL/UL with a text block
  14814. dom.replace(newBlock, containerBlock);
  14815. }
  14816. } else if (isFirstOrLastLi(true)) {
  14817. if (containerBlockParentName == 'LI') {
  14818. // List nested in an LI then move the list to a new sibling LI
  14819. dom.insertAfter(newBlock, getContainerBlock());
  14820. newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
  14821. newBlock.appendChild(containerBlock);
  14822. } else {
  14823. // First LI in list then remove LI and add text block before list
  14824. containerBlock.parentNode.insertBefore(newBlock, containerBlock);
  14825. }
  14826. } else if (isFirstOrLastLi()) {
  14827. // Last LI in list then remove LI and add text block after list
  14828. dom.insertAfter(newBlock, getContainerBlock());
  14829. renderBlockOnIE(newBlock);
  14830. } else {
  14831. // Middle LI in list the split the list and insert a text block in the middle
  14832. // Extract after fragment and insert it after the current block
  14833. containerBlock = getContainerBlock();
  14834. tmpRng = rng.cloneRange();
  14835. tmpRng.setStartAfter(parentBlock);
  14836. tmpRng.setEndAfter(containerBlock);
  14837. fragment = tmpRng.extractContents();
  14838. if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
  14839. newBlock = fragment.firstChild;
  14840. dom.insertAfter(fragment, containerBlock);
  14841. } else {
  14842. dom.insertAfter(fragment, containerBlock);
  14843. dom.insertAfter(newBlock, containerBlock);
  14844. }
  14845. }
  14846. dom.remove(parentBlock);
  14847. moveToCaretPosition(newBlock);
  14848. undoManager.add();
  14849. }
  14850. // Walks the parent block to the right and look for BR elements
  14851. function hasRightSideContent() {
  14852. var walker = new TreeWalker(container, parentBlock), node;
  14853. while ((node = walker.next())) {
  14854. if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
  14855. return true;
  14856. }
  14857. }
  14858. }
  14859. // Inserts a BR element if the forced_root_block option is set to false or empty string
  14860. function insertBr() {
  14861. var brElm, extraBr, marker;
  14862. if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
  14863. // Insert extra BR element at the end block elements
  14864. if (!isIE && !hasRightSideContent()) {
  14865. brElm = dom.create('br');
  14866. rng.insertNode(brElm);
  14867. rng.setStartAfter(brElm);
  14868. rng.setEndAfter(brElm);
  14869. extraBr = true;
  14870. }
  14871. }
  14872. brElm = dom.create('br');
  14873. rng.insertNode(brElm);
  14874. // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
  14875. if (isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
  14876. brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
  14877. }
  14878. // Insert temp marker and scroll to that
  14879. marker = dom.create('span', {}, '&nbsp;');
  14880. brElm.parentNode.insertBefore(marker, brElm);
  14881. selection.scrollIntoView(marker);
  14882. dom.remove(marker);
  14883. if (!extraBr) {
  14884. rng.setStartAfter(brElm);
  14885. rng.setEndAfter(brElm);
  14886. } else {
  14887. rng.setStartBefore(brElm);
  14888. rng.setEndBefore(brElm);
  14889. }
  14890. selection.setRng(rng);
  14891. undoManager.add();
  14892. }
  14893. // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
  14894. function trimLeadingLineBreaks(node) {
  14895. do {
  14896. if (node.nodeType === 3) {
  14897. node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
  14898. }
  14899. node = node.firstChild;
  14900. } while (node);
  14901. }
  14902. function getEditableRoot(node) {
  14903. var root = dom.getRoot(), parent, editableRoot;
  14904. // Get all parents until we hit a non editable parent or the root
  14905. parent = node;
  14906. while (parent !== root && dom.getContentEditable(parent) !== "false") {
  14907. if (dom.getContentEditable(parent) === "true") {
  14908. editableRoot = parent;
  14909. }
  14910. parent = parent.parentNode;
  14911. }
  14912. return parent !== root ? editableRoot : root;
  14913. }
  14914. // Adds a BR at the end of blocks that only contains an IMG or INPUT since
  14915. // these might be floated and then they won't expand the block
  14916. function addBrToBlockIfNeeded(block) {
  14917. var lastChild;
  14918. // IE will render the blocks correctly other browsers needs a BR
  14919. if (!isIE) {
  14920. block.normalize(); // Remove empty text nodes that got left behind by the extract
  14921. // Check if the block is empty or contains a floated last child
  14922. lastChild = block.lastChild;
  14923. if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
  14924. dom.add(block, 'br');
  14925. }
  14926. }
  14927. }
  14928. rng = selection.getRng(true);
  14929. // Event is blocked by some other handler for example the lists plugin
  14930. if (evt.isDefaultPrevented()) {
  14931. return;
  14932. }
  14933. // Delete any selected contents
  14934. if (!rng.collapsed) {
  14935. editor.execCommand('Delete');
  14936. return;
  14937. }
  14938. // Setup range items and newBlockName
  14939. new RangeUtils(dom).normalize(rng);
  14940. container = rng.startContainer;
  14941. offset = rng.startOffset;
  14942. newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
  14943. newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
  14944. documentMode = dom.doc.documentMode;
  14945. shiftKey = evt.shiftKey;
  14946. // Resolve node index
  14947. if (container.nodeType == 1 && container.hasChildNodes()) {
  14948. isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
  14949. container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
  14950. if (isAfterLastNodeInContainer && container.nodeType == 3) {
  14951. offset = container.nodeValue.length;
  14952. } else {
  14953. offset = 0;
  14954. }
  14955. }
  14956. // Get editable root node normaly the body element but sometimes a div or span
  14957. editableRoot = getEditableRoot(container);
  14958. // If there is no editable root then enter is done inside a contentEditable false element
  14959. if (!editableRoot) {
  14960. return;
  14961. }
  14962. undoManager.beforeChange();
  14963. // If editable root isn't block nor the root of the editor
  14964. if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
  14965. if (!newBlockName || shiftKey) {
  14966. insertBr();
  14967. }
  14968. return;
  14969. }
  14970. // Wrap the current node and it's sibling in a default block if it's needed.
  14971. // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
  14972. // This won't happen if root blocks are disabled or the shiftKey is pressed
  14973. if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
  14974. container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
  14975. }
  14976. // Find parent block and setup empty block paddings
  14977. parentBlock = dom.getParent(container, dom.isBlock);
  14978. containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
  14979. // Setup block names
  14980. parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  14981. containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
  14982. // Enter inside block contained within a LI then split or insert before/after LI
  14983. if (containerBlockName == 'LI' && !evt.ctrlKey) {
  14984. parentBlock = containerBlock;
  14985. parentBlockName = containerBlockName;
  14986. }
  14987. // Handle enter in LI
  14988. if (parentBlockName == 'LI') {
  14989. if (!newBlockName && shiftKey) {
  14990. insertBr();
  14991. return;
  14992. }
  14993. // Handle enter inside an empty list item
  14994. if (dom.isEmpty(parentBlock)) {
  14995. handleEmptyListItem();
  14996. return;
  14997. }
  14998. }
  14999. // Don't split PRE tags but insert a BR instead easier when writing code samples etc
  15000. if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
  15001. if (!shiftKey) {
  15002. insertBr();
  15003. return;
  15004. }
  15005. } else {
  15006. // If no root block is configured then insert a BR by default or if the shiftKey is pressed
  15007. if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
  15008. insertBr();
  15009. return;
  15010. }
  15011. }
  15012. // If parent block is root then never insert new blocks
  15013. if (newBlockName && parentBlock === editor.getBody()) {
  15014. return;
  15015. }
  15016. // Default block name if it's not configured
  15017. newBlockName = newBlockName || 'P';
  15018. // Insert new block before/after the parent block depending on caret location
  15019. if (isCaretAtStartOrEndOfBlock()) {
  15020. // 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
  15021. if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
  15022. newBlock = createNewBlock(newBlockName);
  15023. } else {
  15024. newBlock = createNewBlock();
  15025. }
  15026. // Split the current container block element if enter is pressed inside an empty inner block element
  15027. if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
  15028. // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
  15029. newBlock = dom.split(containerBlock, parentBlock);
  15030. } else {
  15031. dom.insertAfter(newBlock, parentBlock);
  15032. }
  15033. moveToCaretPosition(newBlock);
  15034. } else if (isCaretAtStartOrEndOfBlock(true)) {
  15035. // Insert new block before
  15036. newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
  15037. renderBlockOnIE(newBlock);
  15038. moveToCaretPosition(parentBlock);
  15039. } else {
  15040. // Extract after fragment and insert it after the current block
  15041. tmpRng = rng.cloneRange();
  15042. tmpRng.setEndAfter(parentBlock);
  15043. fragment = tmpRng.extractContents();
  15044. trimLeadingLineBreaks(fragment);
  15045. newBlock = fragment.firstChild;
  15046. dom.insertAfter(fragment, parentBlock);
  15047. trimInlineElementsOnLeftSideOfBlock(newBlock);
  15048. addBrToBlockIfNeeded(parentBlock);
  15049. moveToCaretPosition(newBlock);
  15050. }
  15051. dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
  15052. // Allow custom handling of new blocks
  15053. editor.fire('NewBlock', { newBlock: newBlock });
  15054. undoManager.add();
  15055. }
  15056. editor.on('keydown', function(evt) {
  15057. if (evt.keyCode == 13) {
  15058. if (handleEnterKey(evt) !== false) {
  15059. evt.preventDefault();
  15060. }
  15061. }
  15062. });
  15063. };
  15064. });
  15065. // Included from: js/tinymce/classes/ForceBlocks.js
  15066. /**
  15067. * ForceBlocks.js
  15068. *
  15069. * Copyright, Moxiecode Systems AB
  15070. * Released under LGPL License.
  15071. *
  15072. * License: http://www.tinymce.com/license
  15073. * Contributing: http://www.tinymce.com/contributing
  15074. */
  15075. define("tinymce/ForceBlocks", [], function() {
  15076. return function(editor) {
  15077. var settings = editor.settings, dom = editor.dom, selection = editor.selection;
  15078. var schema = editor.schema, blockElements = schema.getBlockElements();
  15079. function addRootBlocks() {
  15080. var node = selection.getStart(), rootNode = editor.getBody(), rng;
  15081. var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
  15082. var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
  15083. var tmpRng, rootNodeName, forcedRootBlock;
  15084. forcedRootBlock = settings.forced_root_block;
  15085. if (!node || node.nodeType !== 1 || !forcedRootBlock) {
  15086. return;
  15087. }
  15088. // Check if node is wrapped in block
  15089. while (node && node != rootNode) {
  15090. if (blockElements[node.nodeName]) {
  15091. return;
  15092. }
  15093. node = node.parentNode;
  15094. }
  15095. // Get current selection
  15096. rng = selection.getRng();
  15097. if (rng.setStart) {
  15098. startContainer = rng.startContainer;
  15099. startOffset = rng.startOffset;
  15100. endContainer = rng.endContainer;
  15101. endOffset = rng.endOffset;
  15102. try {
  15103. restoreSelection = editor.getDoc().activeElement === rootNode;
  15104. } catch (ex) {
  15105. // IE throws unspecified error here sometimes
  15106. }
  15107. } else {
  15108. // Force control range into text range
  15109. if (rng.item) {
  15110. node = rng.item(0);
  15111. rng = editor.getDoc().body.createTextRange();
  15112. rng.moveToElementText(node);
  15113. }
  15114. restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
  15115. tmpRng = rng.duplicate();
  15116. tmpRng.collapse(true);
  15117. startOffset = tmpRng.move('character', offset) * -1;
  15118. if (!tmpRng.collapsed) {
  15119. tmpRng = rng.duplicate();
  15120. tmpRng.collapse(false);
  15121. endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
  15122. }
  15123. }
  15124. // Wrap non block elements and text nodes
  15125. node = rootNode.firstChild;
  15126. rootNodeName = rootNode.nodeName.toLowerCase();
  15127. while (node) {
  15128. // TODO: Break this up, too complex
  15129. if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
  15130. schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
  15131. // Remove empty text nodes
  15132. if (node.nodeType === 3 && node.nodeValue.length === 0) {
  15133. tempNode = node;
  15134. node = node.nextSibling;
  15135. dom.remove(tempNode);
  15136. continue;
  15137. }
  15138. if (!rootBlockNode) {
  15139. rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
  15140. node.parentNode.insertBefore(rootBlockNode, node);
  15141. wrapped = true;
  15142. }
  15143. tempNode = node;
  15144. node = node.nextSibling;
  15145. rootBlockNode.appendChild(tempNode);
  15146. } else {
  15147. rootBlockNode = null;
  15148. node = node.nextSibling;
  15149. }
  15150. }
  15151. if (wrapped && restoreSelection) {
  15152. if (rng.setStart) {
  15153. rng.setStart(startContainer, startOffset);
  15154. rng.setEnd(endContainer, endOffset);
  15155. selection.setRng(rng);
  15156. } else {
  15157. // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
  15158. try {
  15159. rng = editor.getDoc().body.createTextRange();
  15160. rng.moveToElementText(rootNode);
  15161. rng.collapse(true);
  15162. rng.moveStart('character', startOffset);
  15163. if (endOffset > 0) {
  15164. rng.moveEnd('character', endOffset);
  15165. }
  15166. rng.select();
  15167. } catch (ex) {
  15168. // Ignore
  15169. }
  15170. }
  15171. editor.nodeChanged();
  15172. }
  15173. }
  15174. // Force root blocks
  15175. if (settings.forced_root_block) {
  15176. editor.on('NodeChange', addRootBlocks);
  15177. }
  15178. };
  15179. });
  15180. // Included from: js/tinymce/classes/EditorCommands.js
  15181. /**
  15182. * EditorCommands.js
  15183. *
  15184. * Copyright, Moxiecode Systems AB
  15185. * Released under LGPL License.
  15186. *
  15187. * License: http://www.tinymce.com/license
  15188. * Contributing: http://www.tinymce.com/contributing
  15189. */
  15190. /**
  15191. * This class enables you to add custom editor commands and it contains
  15192. * overrides for native browser commands to address various bugs and issues.
  15193. *
  15194. * @class tinymce.EditorCommands
  15195. */
  15196. define("tinymce/EditorCommands", [
  15197. "tinymce/html/Serializer",
  15198. "tinymce/Env",
  15199. "tinymce/util/Tools"
  15200. ], function(Serializer, Env, Tools) {
  15201. // Added for compression purposes
  15202. var each = Tools.each, extend = Tools.extend;
  15203. var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
  15204. var isGecko = Env.gecko, isIE = Env.ie;
  15205. var TRUE = true, FALSE = false;
  15206. return function(editor) {
  15207. var dom = editor.dom,
  15208. selection = editor.selection,
  15209. commands = {state: {}, exec: {}, value: {}},
  15210. settings = editor.settings,
  15211. formatter = editor.formatter,
  15212. bookmark;
  15213. /**
  15214. * Executes the specified command.
  15215. *
  15216. * @method execCommand
  15217. * @param {String} command Command to execute.
  15218. * @param {Boolean} ui Optional user interface state.
  15219. * @param {Object} value Optional value for command.
  15220. * @return {Boolean} true/false if the command was found or not.
  15221. */
  15222. function execCommand(command, ui, value) {
  15223. var func;
  15224. command = command.toLowerCase();
  15225. if ((func = commands.exec[command])) {
  15226. func(command, ui, value);
  15227. return TRUE;
  15228. }
  15229. return FALSE;
  15230. }
  15231. /**
  15232. * Queries the current state for a command for example if the current selection is "bold".
  15233. *
  15234. * @method queryCommandState
  15235. * @param {String} command Command to check the state of.
  15236. * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
  15237. */
  15238. function queryCommandState(command) {
  15239. var func;
  15240. command = command.toLowerCase();
  15241. if ((func = commands.state[command])) {
  15242. return func(command);
  15243. }
  15244. return -1;
  15245. }
  15246. /**
  15247. * Queries the command value for example the current fontsize.
  15248. *
  15249. * @method queryCommandValue
  15250. * @param {String} command Command to check the value of.
  15251. * @return {Object} Command value of false if it's not found.
  15252. */
  15253. function queryCommandValue(command) {
  15254. var func;
  15255. command = command.toLowerCase();
  15256. if ((func = commands.value[command])) {
  15257. return func(command);
  15258. }
  15259. return FALSE;
  15260. }
  15261. /**
  15262. * Adds commands to the command collection.
  15263. *
  15264. * @method addCommands
  15265. * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
  15266. * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
  15267. */
  15268. function addCommands(command_list, type) {
  15269. type = type || 'exec';
  15270. each(command_list, function(callback, command) {
  15271. each(command.toLowerCase().split(','), function(command) {
  15272. commands[type][command] = callback;
  15273. });
  15274. });
  15275. }
  15276. // Expose public methods
  15277. extend(this, {
  15278. execCommand: execCommand,
  15279. queryCommandState: queryCommandState,
  15280. queryCommandValue: queryCommandValue,
  15281. addCommands: addCommands
  15282. });
  15283. // Private methods
  15284. function execNativeCommand(command, ui, value) {
  15285. if (ui === undefined) {
  15286. ui = FALSE;
  15287. }
  15288. if (value === undefined) {
  15289. value = null;
  15290. }
  15291. return editor.getDoc().execCommand(command, ui, value);
  15292. }
  15293. function isFormatMatch(name) {
  15294. return formatter.match(name);
  15295. }
  15296. function toggleFormat(name, value) {
  15297. formatter.toggle(name, value ? {value: value} : undefined);
  15298. editor.nodeChanged();
  15299. }
  15300. function storeSelection(type) {
  15301. bookmark = selection.getBookmark(type);
  15302. }
  15303. function restoreSelection() {
  15304. selection.moveToBookmark(bookmark);
  15305. }
  15306. // Add execCommand overrides
  15307. addCommands({
  15308. // Ignore these, added for compatibility
  15309. 'mceResetDesignMode,mceBeginUndoLevel': function() {},
  15310. // Add undo manager logic
  15311. 'mceEndUndoLevel,mceAddUndoLevel': function() {
  15312. editor.undoManager.add();
  15313. },
  15314. 'Cut,Copy,Paste': function(command) {
  15315. var doc = editor.getDoc(), failed;
  15316. // Try executing the native command
  15317. try {
  15318. execNativeCommand(command);
  15319. } catch (ex) {
  15320. // Command failed
  15321. failed = TRUE;
  15322. }
  15323. // Present alert message about clipboard access not being available
  15324. if (failed || !doc.queryCommandSupported(command)) {
  15325. var msg = editor.translate(
  15326. "Your browser doesn't support direct access to the clipboard. " +
  15327. "Please use the Ctrl+X/C/V keyboard shortcuts instead."
  15328. );
  15329. if (Env.mac) {
  15330. msg = msg.replace(/Ctrl\+/g, '\u2318+');
  15331. }
  15332. editor.windowManager.alert(msg);
  15333. }
  15334. },
  15335. // Override unlink command
  15336. unlink: function() {
  15337. if (selection.isCollapsed()) {
  15338. var elm = selection.getNode();
  15339. if (elm.tagName == 'A') {
  15340. editor.dom.remove(elm, true);
  15341. }
  15342. return;
  15343. }
  15344. formatter.remove("link");
  15345. },
  15346. // Override justify commands to use the text formatter engine
  15347. 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  15348. var align = command.substring(7);
  15349. if (align == 'full') {
  15350. align = 'justify';
  15351. }
  15352. // Remove all other alignments first
  15353. each('left,center,right,justify'.split(','), function(name) {
  15354. if (align != name) {
  15355. formatter.remove('align' + name);
  15356. }
  15357. });
  15358. toggleFormat('align' + align);
  15359. execCommand('mceRepaint');
  15360. },
  15361. // Override list commands to fix WebKit bug
  15362. 'InsertUnorderedList,InsertOrderedList': function(command) {
  15363. var listElm, listParent;
  15364. execNativeCommand(command);
  15365. // WebKit produces lists within block elements so we need to split them
  15366. // we will replace the native list creation logic to custom logic later on
  15367. // TODO: Remove this when the list creation logic is removed
  15368. listElm = dom.getParent(selection.getNode(), 'ol,ul');
  15369. if (listElm) {
  15370. listParent = listElm.parentNode;
  15371. // If list is within a text block then split that block
  15372. if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
  15373. storeSelection();
  15374. dom.split(listParent, listElm);
  15375. restoreSelection();
  15376. }
  15377. }
  15378. },
  15379. // Override commands to use the text formatter engine
  15380. 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  15381. toggleFormat(command);
  15382. },
  15383. // Override commands to use the text formatter engine
  15384. 'ForeColor,HiliteColor,FontName': function(command, ui, value) {
  15385. toggleFormat(command, value);
  15386. },
  15387. FontSize: function(command, ui, value) {
  15388. var fontClasses, fontSizes;
  15389. // Convert font size 1-7 to styles
  15390. if (value >= 1 && value <= 7) {
  15391. fontSizes = explode(settings.font_size_style_values);
  15392. fontClasses = explode(settings.font_size_classes);
  15393. if (fontClasses) {
  15394. value = fontClasses[value - 1] || value;
  15395. } else {
  15396. value = fontSizes[value - 1] || value;
  15397. }
  15398. }
  15399. toggleFormat(command, value);
  15400. },
  15401. RemoveFormat: function(command) {
  15402. formatter.remove(command);
  15403. },
  15404. mceBlockQuote: function() {
  15405. toggleFormat('blockquote');
  15406. },
  15407. FormatBlock: function(command, ui, value) {
  15408. return toggleFormat(value || 'p');
  15409. },
  15410. mceCleanup: function() {
  15411. var bookmark = selection.getBookmark();
  15412. editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
  15413. selection.moveToBookmark(bookmark);
  15414. },
  15415. mceRemoveNode: function(command, ui, value) {
  15416. var node = value || selection.getNode();
  15417. // Make sure that the body node isn't removed
  15418. if (node != editor.getBody()) {
  15419. storeSelection();
  15420. editor.dom.remove(node, TRUE);
  15421. restoreSelection();
  15422. }
  15423. },
  15424. mceSelectNodeDepth: function(command, ui, value) {
  15425. var counter = 0;
  15426. dom.getParent(selection.getNode(), function(node) {
  15427. if (node.nodeType == 1 && counter++ == value) {
  15428. selection.select(node);
  15429. return FALSE;
  15430. }
  15431. }, editor.getBody());
  15432. },
  15433. mceSelectNode: function(command, ui, value) {
  15434. selection.select(value);
  15435. },
  15436. mceInsertContent: function(command, ui, value) {
  15437. var parser, serializer, parentNode, rootNode, fragment, args;
  15438. var marker, rng, node, node2, bookmarkHtml;
  15439. function trimOrPaddLeftRight(html) {
  15440. var rng, container, offset;
  15441. rng = selection.getRng(true);
  15442. container = rng.startContainer;
  15443. offset = rng.startOffset;
  15444. function hasSiblingText(siblingName) {
  15445. return container[siblingName] && container[siblingName].nodeType == 3;
  15446. }
  15447. if (container.nodeType == 3) {
  15448. if (offset > 0) {
  15449. html = html.replace(/^&nbsp;/, ' ');
  15450. } else if (!hasSiblingText('previousSibling')) {
  15451. html = html.replace(/^ /, '&nbsp;');
  15452. }
  15453. if (offset < container.length) {
  15454. html = html.replace(/&nbsp;(<br>|)$/, ' ');
  15455. } else if (!hasSiblingText('nextSibling')) {
  15456. html = html.replace(/(&nbsp;| )(<br>|)$/, '&nbsp;');
  15457. }
  15458. }
  15459. return html;
  15460. }
  15461. // Check for whitespace before/after value
  15462. if (/^ | $/.test(value)) {
  15463. value = trimOrPaddLeftRight(value);
  15464. }
  15465. // Setup parser and serializer
  15466. parser = editor.parser;
  15467. serializer = new Serializer({}, editor.schema);
  15468. bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;&#200B;</span>';
  15469. // Run beforeSetContent handlers on the HTML to be inserted
  15470. args = {content: value, format: 'html', selection: true};
  15471. editor.fire('BeforeSetContent', args);
  15472. value = args.content;
  15473. // Add caret at end of contents if it's missing
  15474. if (value.indexOf('{$caret}') == -1) {
  15475. value += '{$caret}';
  15476. }
  15477. // Replace the caret marker with a span bookmark element
  15478. value = value.replace(/\{\$caret\}/, bookmarkHtml);
  15479. // If selection is at <body>|<p></p> then move it into <body><p>|</p>
  15480. rng = selection.getRng();
  15481. var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
  15482. var body = editor.getBody();
  15483. if (caretElement === body && selection.isCollapsed()) {
  15484. if (dom.isBlock(body.firstChild) && dom.isEmpty(body.firstChild)) {
  15485. rng = dom.createRng();
  15486. rng.setStart(body.firstChild, 0);
  15487. rng.setEnd(body.firstChild, 0);
  15488. selection.setRng(rng);
  15489. }
  15490. }
  15491. // Insert node maker where we will insert the new HTML and get it's parent
  15492. if (!selection.isCollapsed()) {
  15493. editor.getDoc().execCommand('Delete', false, null);
  15494. }
  15495. parentNode = selection.getNode();
  15496. // Parse the fragment within the context of the parent node
  15497. var parserArgs = {context: parentNode.nodeName.toLowerCase()};
  15498. fragment = parser.parse(value, parserArgs);
  15499. // Move the caret to a more suitable location
  15500. node = fragment.lastChild;
  15501. if (node.attr('id') == 'mce_marker') {
  15502. marker = node;
  15503. for (node = node.prev; node; node = node.walk(true)) {
  15504. if (node.type == 3 || !dom.isBlock(node.name)) {
  15505. node.parent.insert(marker, node, node.name === 'br');
  15506. break;
  15507. }
  15508. }
  15509. }
  15510. // If parser says valid we can insert the contents into that parent
  15511. if (!parserArgs.invalid) {
  15512. value = serializer.serialize(fragment);
  15513. // Check if parent is empty or only has one BR element then set the innerHTML of that parent
  15514. node = parentNode.firstChild;
  15515. node2 = parentNode.lastChild;
  15516. if (!node || (node === node2 && node.nodeName === 'BR')) {
  15517. dom.setHTML(parentNode, value);
  15518. } else {
  15519. selection.setContent(value);
  15520. }
  15521. } else {
  15522. // If the fragment was invalid within that context then we need
  15523. // to parse and process the parent it's inserted into
  15524. // Insert bookmark node and get the parent
  15525. selection.setContent(bookmarkHtml);
  15526. parentNode = selection.getNode();
  15527. rootNode = editor.getBody();
  15528. // Opera will return the document node when selection is in root
  15529. if (parentNode.nodeType == 9) {
  15530. parentNode = node = rootNode;
  15531. } else {
  15532. node = parentNode;
  15533. }
  15534. // Find the ancestor just before the root element
  15535. while (node !== rootNode) {
  15536. parentNode = node;
  15537. node = node.parentNode;
  15538. }
  15539. // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
  15540. value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
  15541. value = serializer.serialize(
  15542. parser.parse(
  15543. // Need to replace by using a function since $ in the contents would otherwise be a problem
  15544. value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
  15545. return serializer.serialize(fragment);
  15546. })
  15547. )
  15548. );
  15549. // Set the inner/outer HTML depending on if we are in the root or not
  15550. if (parentNode == rootNode) {
  15551. dom.setHTML(rootNode, value);
  15552. } else {
  15553. dom.setOuterHTML(parentNode, value);
  15554. }
  15555. }
  15556. marker = dom.get('mce_marker');
  15557. selection.scrollIntoView(marker);
  15558. // Move selection before marker and remove it
  15559. rng = dom.createRng();
  15560. // If previous sibling is a text node set the selection to the end of that node
  15561. node = marker.previousSibling;
  15562. if (node && node.nodeType == 3) {
  15563. rng.setStart(node, node.nodeValue.length);
  15564. // TODO: Why can't we normalize on IE
  15565. if (!isIE) {
  15566. node2 = marker.nextSibling;
  15567. if (node2 && node2.nodeType == 3) {
  15568. node.appendData(node2.data);
  15569. node2.parentNode.removeChild(node2);
  15570. }
  15571. }
  15572. } else {
  15573. // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
  15574. rng.setStartBefore(marker);
  15575. rng.setEndBefore(marker);
  15576. }
  15577. // Remove the marker node and set the new range
  15578. dom.remove(marker);
  15579. selection.setRng(rng);
  15580. // Dispatch after event and add any visual elements needed
  15581. editor.fire('SetContent', args);
  15582. editor.addVisual();
  15583. },
  15584. mceInsertRawHTML: function(command, ui, value) {
  15585. selection.setContent('tiny_mce_marker');
  15586. editor.setContent(
  15587. editor.getContent().replace(/tiny_mce_marker/g, function() {
  15588. return value;
  15589. })
  15590. );
  15591. },
  15592. mceToggleFormat: function(command, ui, value) {
  15593. toggleFormat(value);
  15594. },
  15595. mceSetContent: function(command, ui, value) {
  15596. editor.setContent(value);
  15597. },
  15598. 'Indent,Outdent': function(command) {
  15599. var intentValue, indentUnit, value;
  15600. // Setup indent level
  15601. intentValue = settings.indentation;
  15602. indentUnit = /[a-z%]+$/i.exec(intentValue);
  15603. intentValue = parseInt(intentValue, 10);
  15604. if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
  15605. // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
  15606. if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
  15607. formatter.apply('div');
  15608. }
  15609. each(selection.getSelectedBlocks(), function(element) {
  15610. if (element.nodeName != "LI") {
  15611. var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
  15612. indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
  15613. if (command == 'outdent') {
  15614. value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
  15615. dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
  15616. } else {
  15617. value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
  15618. dom.setStyle(element, indentStyleName, value);
  15619. }
  15620. }
  15621. });
  15622. } else {
  15623. execNativeCommand(command);
  15624. }
  15625. },
  15626. mceRepaint: function() {
  15627. if (isGecko) {
  15628. try {
  15629. storeSelection(TRUE);
  15630. if (selection.getSel()) {
  15631. selection.getSel().selectAllChildren(editor.getBody());
  15632. }
  15633. selection.collapse(TRUE);
  15634. restoreSelection();
  15635. } catch (ex) {
  15636. // Ignore
  15637. }
  15638. }
  15639. },
  15640. InsertHorizontalRule: function() {
  15641. editor.execCommand('mceInsertContent', false, '<hr />');
  15642. },
  15643. mceToggleVisualAid: function() {
  15644. editor.hasVisual = !editor.hasVisual;
  15645. editor.addVisual();
  15646. },
  15647. mceReplaceContent: function(command, ui, value) {
  15648. editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
  15649. },
  15650. mceInsertLink: function(command, ui, value) {
  15651. var anchor;
  15652. if (typeof(value) == 'string') {
  15653. value = {href: value};
  15654. }
  15655. anchor = dom.getParent(selection.getNode(), 'a');
  15656. // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
  15657. value.href = value.href.replace(' ', '%20');
  15658. // Remove existing links if there could be child links or that the href isn't specified
  15659. if (!anchor || !value.href) {
  15660. formatter.remove('link');
  15661. }
  15662. // Apply new link to selection
  15663. if (value.href) {
  15664. formatter.apply('link', value, anchor);
  15665. }
  15666. },
  15667. selectAll: function() {
  15668. var root = dom.getRoot(), rng;
  15669. if (selection.getRng().setStart) {
  15670. rng = dom.createRng();
  15671. rng.setStart(root, 0);
  15672. rng.setEnd(root, root.childNodes.length);
  15673. selection.setRng(rng);
  15674. } else {
  15675. // IE will render it's own root level block elements and sometimes
  15676. // even put font elements in them when the user starts typing. So we need to
  15677. // move the selection to a more suitable element from this:
  15678. // <body>|<p></p></body> to this: <body><p>|</p></body>
  15679. rng = selection.getRng();
  15680. if (!rng.item) {
  15681. rng.moveToElementText(root);
  15682. rng.select();
  15683. }
  15684. }
  15685. },
  15686. "delete": function() {
  15687. execNativeCommand("Delete");
  15688. // Check if body is empty after the delete call if so then set the contents
  15689. // to an empty string and move the caret to any block produced by that operation
  15690. // this fixes the issue with root blocks not being properly produced after a delete call on IE
  15691. var body = editor.getBody();
  15692. if (dom.isEmpty(body)) {
  15693. editor.setContent('');
  15694. if (body.firstChild && dom.isBlock(body.firstChild)) {
  15695. editor.selection.setCursorLocation(body.firstChild, 0);
  15696. } else {
  15697. editor.selection.setCursorLocation(body, 0);
  15698. }
  15699. }
  15700. },
  15701. mceNewDocument: function() {
  15702. editor.setContent('');
  15703. }
  15704. });
  15705. // Add queryCommandState overrides
  15706. addCommands({
  15707. // Override justify commands
  15708. 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
  15709. var name = 'align' + command.substring(7);
  15710. var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
  15711. var matches = map(nodes, function(node) {
  15712. return !!formatter.matchNode(node, name);
  15713. });
  15714. return inArray(matches, TRUE) !== -1;
  15715. },
  15716. 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
  15717. return isFormatMatch(command);
  15718. },
  15719. mceBlockQuote: function() {
  15720. return isFormatMatch('blockquote');
  15721. },
  15722. Outdent: function() {
  15723. var node;
  15724. if (settings.inline_styles) {
  15725. if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  15726. return TRUE;
  15727. }
  15728. if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
  15729. return TRUE;
  15730. }
  15731. }
  15732. return (
  15733. queryCommandState('InsertUnorderedList') ||
  15734. queryCommandState('InsertOrderedList') ||
  15735. (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
  15736. );
  15737. },
  15738. 'InsertUnorderedList,InsertOrderedList': function(command) {
  15739. var list = dom.getParent(selection.getNode(), 'ul,ol');
  15740. return list &&
  15741. (
  15742. command === 'insertunorderedlist' && list.tagName === 'UL' ||
  15743. command === 'insertorderedlist' && list.tagName === 'OL'
  15744. );
  15745. }
  15746. }, 'state');
  15747. // Add queryCommandValue overrides
  15748. addCommands({
  15749. 'FontSize,FontName': function(command) {
  15750. var value = 0, parent;
  15751. if ((parent = dom.getParent(selection.getNode(), 'span'))) {
  15752. if (command == 'fontsize') {
  15753. value = parent.style.fontSize;
  15754. } else {
  15755. value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
  15756. }
  15757. }
  15758. return value;
  15759. }
  15760. }, 'value');
  15761. // Add undo manager logic
  15762. addCommands({
  15763. Undo: function() {
  15764. editor.undoManager.undo();
  15765. },
  15766. Redo: function() {
  15767. editor.undoManager.redo();
  15768. }
  15769. });
  15770. };
  15771. });
  15772. // Included from: js/tinymce/classes/util/URI.js
  15773. /**
  15774. * URI.js
  15775. *
  15776. * Copyright, Moxiecode Systems AB
  15777. * Released under LGPL License.
  15778. *
  15779. * License: http://www.tinymce.com/license
  15780. * Contributing: http://www.tinymce.com/contributing
  15781. */
  15782. /**
  15783. * This class handles parsing, modification and serialization of URI/URL strings.
  15784. * @class tinymce.util.URI
  15785. */
  15786. define("tinymce/util/URI", [
  15787. "tinymce/util/Tools"
  15788. ], function(Tools) {
  15789. var each = Tools.each, trim = Tools.trim;
  15790. /**
  15791. * Constructs a new URI instance.
  15792. *
  15793. * @constructor
  15794. * @method URI
  15795. * @param {String} url URI string to parse.
  15796. * @param {Object} settings Optional settings object.
  15797. */
  15798. function URI(url, settings) {
  15799. var self = this, baseUri, base_url;
  15800. // Trim whitespace
  15801. url = trim(url);
  15802. // Default settings
  15803. settings = self.settings = settings || {};
  15804. // Strange app protocol that isn't http/https or local anchor
  15805. // For example: mailto,skype,tel etc.
  15806. if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
  15807. self.source = url;
  15808. return;
  15809. }
  15810. var isProtocolRelative = url.indexOf('//') === 0;
  15811. // Absolute path with no host, fake host and protocol
  15812. if (url.indexOf('/') === 0 && !isProtocolRelative) {
  15813. url = (settings.base_uri ? settings.base_uri.protocol || 'http' : 'http') + '://mce_host' + url;
  15814. }
  15815. // Relative path http:// or protocol relative //path
  15816. if (!/^[\w\-]*:?\/\//.test(url)) {
  15817. base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
  15818. if (settings.base_uri.protocol === "") {
  15819. url = '//mce_host' + self.toAbsPath(base_url, url);
  15820. } else {
  15821. url = ((settings.base_uri && settings.base_uri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url);
  15822. }
  15823. }
  15824. // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
  15825. url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
  15826. /*jshint maxlen: 255 */
  15827. /*eslint max-len: 0 */
  15828. url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
  15829. each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
  15830. var part = url[i];
  15831. // Zope 3 workaround, they use @@something
  15832. if (part) {
  15833. part = part.replace(/\(mce_at\)/g, '@@');
  15834. }
  15835. self[v] = part;
  15836. });
  15837. baseUri = settings.base_uri;
  15838. if (baseUri) {
  15839. if (!self.protocol) {
  15840. self.protocol = baseUri.protocol;
  15841. }
  15842. if (!self.userInfo) {
  15843. self.userInfo = baseUri.userInfo;
  15844. }
  15845. if (!self.port && self.host === 'mce_host') {
  15846. self.port = baseUri.port;
  15847. }
  15848. if (!self.host || self.host === 'mce_host') {
  15849. self.host = baseUri.host;
  15850. }
  15851. self.source = '';
  15852. }
  15853. if (isProtocolRelative) {
  15854. self.protocol = '';
  15855. }
  15856. //t.path = t.path || '/';
  15857. }
  15858. URI.prototype = {
  15859. /**
  15860. * Sets the internal path part of the URI.
  15861. *
  15862. * @method setPath
  15863. * @param {string} path Path string to set.
  15864. */
  15865. setPath: function(path) {
  15866. var self = this;
  15867. path = /^(.*?)\/?(\w+)?$/.exec(path);
  15868. // Update path parts
  15869. self.path = path[0];
  15870. self.directory = path[1];
  15871. self.file = path[2];
  15872. // Rebuild source
  15873. self.source = '';
  15874. self.getURI();
  15875. },
  15876. /**
  15877. * Converts the specified URI into a relative URI based on the current URI instance location.
  15878. *
  15879. * @method toRelative
  15880. * @param {String} uri URI to convert into a relative path/URI.
  15881. * @return {String} Relative URI from the point specified in the current URI instance.
  15882. * @example
  15883. * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
  15884. * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
  15885. */
  15886. toRelative: function(uri) {
  15887. var self = this, output;
  15888. if (uri === "./") {
  15889. return uri;
  15890. }
  15891. uri = new URI(uri, {base_uri: self});
  15892. // Not on same domain/port or protocol
  15893. if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
  15894. (self.protocol != uri.protocol && uri.protocol !== "")) {
  15895. return uri.getURI();
  15896. }
  15897. var tu = self.getURI(), uu = uri.getURI();
  15898. // Allow usage of the base_uri when relative_urls = true
  15899. if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
  15900. return tu;
  15901. }
  15902. output = self.toRelPath(self.path, uri.path);
  15903. // Add query
  15904. if (uri.query) {
  15905. output += '?' + uri.query;
  15906. }
  15907. // Add anchor
  15908. if (uri.anchor) {
  15909. output += '#' + uri.anchor;
  15910. }
  15911. return output;
  15912. },
  15913. /**
  15914. * Converts the specified URI into a absolute URI based on the current URI instance location.
  15915. *
  15916. * @method toAbsolute
  15917. * @param {String} uri URI to convert into a relative path/URI.
  15918. * @param {Boolean} noHost No host and protocol prefix.
  15919. * @return {String} Absolute URI from the point specified in the current URI instance.
  15920. * @example
  15921. * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
  15922. * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
  15923. */
  15924. toAbsolute: function(uri, noHost) {
  15925. uri = new URI(uri, {base_uri: this});
  15926. return uri.getURI(this.host == uri.host && this.protocol == uri.protocol ? noHost : 0);
  15927. },
  15928. /**
  15929. * Converts a absolute path into a relative path.
  15930. *
  15931. * @method toRelPath
  15932. * @param {String} base Base point to convert the path from.
  15933. * @param {String} path Absolute path to convert into a relative path.
  15934. */
  15935. toRelPath: function(base, path) {
  15936. var items, breakPoint = 0, out = '', i, l;
  15937. // Split the paths
  15938. base = base.substring(0, base.lastIndexOf('/'));
  15939. base = base.split('/');
  15940. items = path.split('/');
  15941. if (base.length >= items.length) {
  15942. for (i = 0, l = base.length; i < l; i++) {
  15943. if (i >= items.length || base[i] != items[i]) {
  15944. breakPoint = i + 1;
  15945. break;
  15946. }
  15947. }
  15948. }
  15949. if (base.length < items.length) {
  15950. for (i = 0, l = items.length; i < l; i++) {
  15951. if (i >= base.length || base[i] != items[i]) {
  15952. breakPoint = i + 1;
  15953. break;
  15954. }
  15955. }
  15956. }
  15957. if (breakPoint === 1) {
  15958. return path;
  15959. }
  15960. for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
  15961. out += "../";
  15962. }
  15963. for (i = breakPoint - 1, l = items.length; i < l; i++) {
  15964. if (i != breakPoint - 1) {
  15965. out += "/" + items[i];
  15966. } else {
  15967. out += items[i];
  15968. }
  15969. }
  15970. return out;
  15971. },
  15972. /**
  15973. * Converts a relative path into a absolute path.
  15974. *
  15975. * @method toAbsPath
  15976. * @param {String} base Base point to convert the path from.
  15977. * @param {String} path Relative path to convert into an absolute path.
  15978. */
  15979. toAbsPath: function(base, path) {
  15980. var i, nb = 0, o = [], tr, outPath;
  15981. // Split paths
  15982. tr = /\/$/.test(path) ? '/' : '';
  15983. base = base.split('/');
  15984. path = path.split('/');
  15985. // Remove empty chunks
  15986. each(base, function(k) {
  15987. if (k) {
  15988. o.push(k);
  15989. }
  15990. });
  15991. base = o;
  15992. // Merge relURLParts chunks
  15993. for (i = path.length - 1, o = []; i >= 0; i--) {
  15994. // Ignore empty or .
  15995. if (path[i].length === 0 || path[i] === ".") {
  15996. continue;
  15997. }
  15998. // Is parent
  15999. if (path[i] === '..') {
  16000. nb++;
  16001. continue;
  16002. }
  16003. // Move up
  16004. if (nb > 0) {
  16005. nb--;
  16006. continue;
  16007. }
  16008. o.push(path[i]);
  16009. }
  16010. i = base.length - nb;
  16011. // If /a/b/c or /
  16012. if (i <= 0) {
  16013. outPath = o.reverse().join('/');
  16014. } else {
  16015. outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
  16016. }
  16017. // Add front / if it's needed
  16018. if (outPath.indexOf('/') !== 0) {
  16019. outPath = '/' + outPath;
  16020. }
  16021. // Add traling / if it's needed
  16022. if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
  16023. outPath += tr;
  16024. }
  16025. return outPath;
  16026. },
  16027. /**
  16028. * Returns the full URI of the internal structure.
  16029. *
  16030. * @method getURI
  16031. * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
  16032. */
  16033. getURI: function(noProtoHost) {
  16034. var s, self = this;
  16035. // Rebuild source
  16036. if (!self.source || noProtoHost) {
  16037. s = '';
  16038. if (!noProtoHost) {
  16039. if (self.protocol) {
  16040. s += self.protocol + '://';
  16041. } else {
  16042. s += '//';
  16043. }
  16044. if (self.userInfo) {
  16045. s += self.userInfo + '@';
  16046. }
  16047. if (self.host) {
  16048. s += self.host;
  16049. }
  16050. if (self.port) {
  16051. s += ':' + self.port;
  16052. }
  16053. }
  16054. if (self.path) {
  16055. s += self.path;
  16056. }
  16057. if (self.query) {
  16058. s += '?' + self.query;
  16059. }
  16060. if (self.anchor) {
  16061. s += '#' + self.anchor;
  16062. }
  16063. self.source = s;
  16064. }
  16065. return self.source;
  16066. }
  16067. };
  16068. return URI;
  16069. });
  16070. // Included from: js/tinymce/classes/util/Class.js
  16071. /**
  16072. * Class.js
  16073. *
  16074. * Copyright 2003-2012, Moxiecode Systems AB, All rights reserved.
  16075. */
  16076. /**
  16077. * This utilitiy class is used for easier inheritage.
  16078. *
  16079. * Features:
  16080. * * Exposed super functions: this._super();
  16081. * * Mixins
  16082. * * Dummy functions
  16083. * * Property functions: var value = object.value(); and object.value(newValue);
  16084. * * Static functions
  16085. * * Defaults settings
  16086. */
  16087. define("tinymce/util/Class", [
  16088. "tinymce/util/Tools"
  16089. ], function(Tools) {
  16090. var each = Tools.each, extend = Tools.extend;
  16091. var extendClass, initializing;
  16092. function Class() {
  16093. }
  16094. // Provides classical inheritance, based on code made by John Resig
  16095. Class.extend = extendClass = function(prop) {
  16096. var self = this, _super = self.prototype, prototype, name, member;
  16097. // The dummy class constructor
  16098. function Class() {
  16099. var i, mixins, mixin, self = this;
  16100. // All construction is actually done in the init method
  16101. if (!initializing) {
  16102. // Run class constuctor
  16103. if (self.init) {
  16104. self.init.apply(self, arguments);
  16105. }
  16106. // Run mixin constructors
  16107. mixins = self.Mixins;
  16108. if (mixins) {
  16109. i = mixins.length;
  16110. while (i--) {
  16111. mixin = mixins[i];
  16112. if (mixin.init) {
  16113. mixin.init.apply(self, arguments);
  16114. }
  16115. }
  16116. }
  16117. }
  16118. }
  16119. // Dummy function, needs to be extended in order to provide functionality
  16120. function dummy() {
  16121. return this;
  16122. }
  16123. // Creates a overloaded method for the class
  16124. // this enables you to use this._super(); to call the super function
  16125. function createMethod(name, fn) {
  16126. return function(){
  16127. var self = this, tmp = self._super, ret;
  16128. self._super = _super[name];
  16129. ret = fn.apply(self, arguments);
  16130. self._super = tmp;
  16131. return ret;
  16132. };
  16133. }
  16134. // Instantiate a base class (but only create the instance,
  16135. // don't run the init constructor)
  16136. initializing = true;
  16137. prototype = new self();
  16138. initializing = false;
  16139. // Add mixins
  16140. if (prop.Mixins) {
  16141. each(prop.Mixins, function(mixin) {
  16142. mixin = mixin;
  16143. for (var name in mixin) {
  16144. if (name !== "init") {
  16145. prop[name] = mixin[name];
  16146. }
  16147. }
  16148. });
  16149. if (_super.Mixins) {
  16150. prop.Mixins = _super.Mixins.concat(prop.Mixins);
  16151. }
  16152. }
  16153. // Generate dummy methods
  16154. if (prop.Methods) {
  16155. each(prop.Methods.split(','), function(name) {
  16156. prop[name] = dummy;
  16157. });
  16158. }
  16159. // Generate property methods
  16160. if (prop.Properties) {
  16161. each(prop.Properties.split(','), function(name) {
  16162. var fieldName = '_' + name;
  16163. prop[name] = function(value) {
  16164. var self = this, undef;
  16165. // Set value
  16166. if (value !== undef) {
  16167. self[fieldName] = value;
  16168. return self;
  16169. }
  16170. // Get value
  16171. return self[fieldName];
  16172. };
  16173. });
  16174. }
  16175. // Static functions
  16176. if (prop.Statics) {
  16177. each(prop.Statics, function(func, name) {
  16178. Class[name] = func;
  16179. });
  16180. }
  16181. // Default settings
  16182. if (prop.Defaults && _super.Defaults) {
  16183. prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
  16184. }
  16185. // Copy the properties over onto the new prototype
  16186. for (name in prop) {
  16187. member = prop[name];
  16188. if (typeof member == "function" && _super[name]) {
  16189. prototype[name] = createMethod(name, member);
  16190. } else {
  16191. prototype[name] = member;
  16192. }
  16193. }
  16194. // Populate our constructed prototype object
  16195. Class.prototype = prototype;
  16196. // Enforce the constructor to be what we expect
  16197. Class.constructor = Class;
  16198. // And make this class extendible
  16199. Class.extend = extendClass;
  16200. return Class;
  16201. };
  16202. return Class;
  16203. });
  16204. // Included from: js/tinymce/classes/ui/Selector.js
  16205. /**
  16206. * Selector.js
  16207. *
  16208. * Copyright, Moxiecode Systems AB
  16209. * Released under LGPL License.
  16210. *
  16211. * License: http://www.tinymce.com/license
  16212. * Contributing: http://www.tinymce.com/contributing
  16213. */
  16214. /*eslint no-nested-ternary:0 */
  16215. /**
  16216. * Selector engine, enables you to select controls by using CSS like expressions.
  16217. * We currently only support basic CSS expressions to reduce the size of the core
  16218. * and the ones we support should be enough for most cases.
  16219. *
  16220. * @example
  16221. * Supported expressions:
  16222. * element
  16223. * element#name
  16224. * element.class
  16225. * element[attr]
  16226. * element[attr*=value]
  16227. * element[attr~=value]
  16228. * element[attr!=value]
  16229. * element[attr^=value]
  16230. * element[attr$=value]
  16231. * element:<state>
  16232. * element:not(<expression>)
  16233. * element:first
  16234. * element:last
  16235. * element:odd
  16236. * element:even
  16237. * element element
  16238. * element > element
  16239. *
  16240. * @class tinymce.ui.Selector
  16241. */
  16242. define("tinymce/ui/Selector", [
  16243. "tinymce/util/Class"
  16244. ], function(Class) {
  16245. "use strict";
  16246. /**
  16247. * Produces an array with a unique set of objects. It will not compare the values
  16248. * but the references of the objects.
  16249. *
  16250. * @private
  16251. * @method unqiue
  16252. * @param {Array} array Array to make into an array with unique items.
  16253. * @return {Array} Array with unique items.
  16254. */
  16255. function unique(array) {
  16256. var uniqueItems = [], i = array.length, item;
  16257. while (i--) {
  16258. item = array[i];
  16259. if (!item.__checked) {
  16260. uniqueItems.push(item);
  16261. item.__checked = 1;
  16262. }
  16263. }
  16264. i = uniqueItems.length;
  16265. while (i--) {
  16266. delete uniqueItems[i].__checked;
  16267. }
  16268. return uniqueItems;
  16269. }
  16270. var expression = /^([\w\\*]+)?(?:#([\w\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
  16271. /*jshint maxlen:255 */
  16272. /*eslint max-len:0 */
  16273. var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
  16274. whiteSpace = /^\s*|\s*$/g,
  16275. Collection;
  16276. var Selector = Class.extend({
  16277. /**
  16278. * Constructs a new Selector instance.
  16279. *
  16280. * @constructor
  16281. * @method init
  16282. * @param {String} selector CSS like selector expression.
  16283. */
  16284. init: function(selector) {
  16285. var match = this.match;
  16286. function compileNameFilter(name) {
  16287. if (name) {
  16288. name = name.toLowerCase();
  16289. return function(item) {
  16290. return name === '*' || item.type === name;
  16291. };
  16292. }
  16293. }
  16294. function compileIdFilter(id) {
  16295. if (id) {
  16296. return function(item) {
  16297. return item._name === id;
  16298. };
  16299. }
  16300. }
  16301. function compileClassesFilter(classes) {
  16302. if (classes) {
  16303. classes = classes.split('.');
  16304. return function(item) {
  16305. var i = classes.length;
  16306. while (i--) {
  16307. if (!item.hasClass(classes[i])) {
  16308. return false;
  16309. }
  16310. }
  16311. return true;
  16312. };
  16313. }
  16314. }
  16315. function compileAttrFilter(name, cmp, check) {
  16316. if (name) {
  16317. return function(item) {
  16318. var value = item[name] ? item[name]() : '';
  16319. return !cmp ? !!check :
  16320. cmp === "=" ? value === check :
  16321. cmp === "*=" ? value.indexOf(check) >= 0 :
  16322. cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
  16323. cmp === "!=" ? value != check :
  16324. cmp === "^=" ? value.indexOf(check) === 0 :
  16325. cmp === "$=" ? value.substr(value.length - check.length) === check :
  16326. false;
  16327. };
  16328. }
  16329. }
  16330. function compilePsuedoFilter(name) {
  16331. var notSelectors;
  16332. if (name) {
  16333. name = /(?:not\((.+)\))|(.+)/i.exec(name);
  16334. if (!name[1]) {
  16335. name = name[2];
  16336. return function(item, index, length) {
  16337. return name === 'first' ? index === 0 :
  16338. name === 'last' ? index === length - 1 :
  16339. name === 'even' ? index % 2 === 0 :
  16340. name === 'odd' ? index % 2 === 1 :
  16341. item[name] ? item[name]() :
  16342. false;
  16343. };
  16344. } else {
  16345. // Compile not expression
  16346. notSelectors = parseChunks(name[1], []);
  16347. return function(item) {
  16348. return !match(item, notSelectors);
  16349. };
  16350. }
  16351. }
  16352. }
  16353. function compile(selector, filters, direct) {
  16354. var parts;
  16355. function add(filter) {
  16356. if (filter) {
  16357. filters.push(filter);
  16358. }
  16359. }
  16360. // Parse expression into parts
  16361. parts = expression.exec(selector.replace(whiteSpace, ''));
  16362. add(compileNameFilter(parts[1]));
  16363. add(compileIdFilter(parts[2]));
  16364. add(compileClassesFilter(parts[3]));
  16365. add(compileAttrFilter(parts[4], parts[5], parts[6]));
  16366. add(compilePsuedoFilter(parts[7]));
  16367. // Mark the filter with psuedo for performance
  16368. filters.psuedo = !!parts[7];
  16369. filters.direct = direct;
  16370. return filters;
  16371. }
  16372. // Parser logic based on Sizzle by John Resig
  16373. function parseChunks(selector, selectors) {
  16374. var parts = [], extra, matches, i;
  16375. do {
  16376. chunker.exec("");
  16377. matches = chunker.exec(selector);
  16378. if (matches) {
  16379. selector = matches[3];
  16380. parts.push(matches[1]);
  16381. if (matches[2]) {
  16382. extra = matches[3];
  16383. break;
  16384. }
  16385. }
  16386. } while (matches);
  16387. if (extra) {
  16388. parseChunks(extra, selectors);
  16389. }
  16390. selector = [];
  16391. for (i = 0; i < parts.length; i++) {
  16392. if (parts[i] != '>') {
  16393. selector.push(compile(parts[i], [], parts[i - 1] === '>'));
  16394. }
  16395. }
  16396. selectors.push(selector);
  16397. return selectors;
  16398. }
  16399. this._selectors = parseChunks(selector, []);
  16400. },
  16401. /**
  16402. * Returns true/false if the selector matches the specified control.
  16403. *
  16404. * @method match
  16405. * @param {tinymce.ui.Control} control Control to match agains the selector.
  16406. * @param {Array} selectors Optional array of selectors, mostly used internally.
  16407. * @return {Boolean} true/false state if the control matches or not.
  16408. */
  16409. match: function(control, selectors) {
  16410. var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
  16411. selectors = selectors || this._selectors;
  16412. for (i = 0, l = selectors.length; i < l; i++) {
  16413. selector = selectors[i];
  16414. sl = selector.length;
  16415. item = control;
  16416. count = 0;
  16417. for (si = sl - 1; si >= 0; si--) {
  16418. filters = selector[si];
  16419. while (item) {
  16420. // Find the index and length since a psuedo filter like :first needs it
  16421. if (filters.psuedo) {
  16422. siblings = item.parent().items();
  16423. index = length = siblings.length;
  16424. while (index--) {
  16425. if (siblings[index] === item) {
  16426. break;
  16427. }
  16428. }
  16429. }
  16430. for (fi = 0, fl = filters.length; fi < fl; fi++) {
  16431. if (!filters[fi](item, index, length)) {
  16432. fi = fl + 1;
  16433. break;
  16434. }
  16435. }
  16436. if (fi === fl) {
  16437. count++;
  16438. break;
  16439. } else {
  16440. // If it didn't match the right most expression then
  16441. // break since it's no point looking at the parents
  16442. if (si === sl - 1) {
  16443. break;
  16444. }
  16445. }
  16446. item = item.parent();
  16447. }
  16448. }
  16449. // If we found all selectors then return true otherwise continue looking
  16450. if (count === sl) {
  16451. return true;
  16452. }
  16453. }
  16454. return false;
  16455. },
  16456. /**
  16457. * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
  16458. *
  16459. * @method find
  16460. * @param {tinymce.ui.Control} container Container to look for items in.
  16461. * @return {tinymce.ui.Collection} Collection with matched elements.
  16462. */
  16463. find: function(container) {
  16464. var matches = [], i, l, selectors = this._selectors;
  16465. function collect(items, selector, index) {
  16466. var i, l, fi, fl, item, filters = selector[index];
  16467. for (i = 0, l = items.length; i < l; i++) {
  16468. item = items[i];
  16469. // Run each filter agains the item
  16470. for (fi = 0, fl = filters.length; fi < fl; fi++) {
  16471. if (!filters[fi](item, i, l)) {
  16472. fi = fl + 1;
  16473. break;
  16474. }
  16475. }
  16476. // All filters matched the item
  16477. if (fi === fl) {
  16478. // Matched item is on the last expression like: panel toolbar [button]
  16479. if (index == selector.length - 1) {
  16480. matches.push(item);
  16481. } else {
  16482. // Collect next expression type
  16483. if (item.items) {
  16484. collect(item.items(), selector, index + 1);
  16485. }
  16486. }
  16487. } else if (filters.direct) {
  16488. return;
  16489. }
  16490. // Collect child items
  16491. if (item.items) {
  16492. collect(item.items(), selector, index);
  16493. }
  16494. }
  16495. }
  16496. if (container.items) {
  16497. for (i = 0, l = selectors.length; i < l; i++) {
  16498. collect(container.items(), selectors[i], 0);
  16499. }
  16500. // Unique the matches if needed
  16501. if (l > 1) {
  16502. matches = unique(matches);
  16503. }
  16504. }
  16505. // Fix for circular reference
  16506. if (!Collection) {
  16507. // TODO: Fix me!
  16508. Collection = Selector.Collection;
  16509. }
  16510. return new Collection(matches);
  16511. }
  16512. });
  16513. return Selector;
  16514. });
  16515. // Included from: js/tinymce/classes/ui/Collection.js
  16516. /**
  16517. * Collection.js
  16518. *
  16519. * Copyright, Moxiecode Systems AB
  16520. * Released under LGPL License.
  16521. *
  16522. * License: http://www.tinymce.com/license
  16523. * Contributing: http://www.tinymce.com/contributing
  16524. */
  16525. /**
  16526. * Control collection, this class contains control instances and it enables you to
  16527. * perform actions on all the contained items. This is very similar to how jQuery works.
  16528. *
  16529. * @example
  16530. * someCollection.show().disabled(true);
  16531. *
  16532. * @class tinymce.ui.Collection
  16533. */
  16534. define("tinymce/ui/Collection", [
  16535. "tinymce/util/Tools",
  16536. "tinymce/ui/Selector",
  16537. "tinymce/util/Class"
  16538. ], function(Tools, Selector, Class) {
  16539. "use strict";
  16540. var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
  16541. proto = {
  16542. /**
  16543. * Current number of contained control instances.
  16544. *
  16545. * @field length
  16546. * @type Number
  16547. */
  16548. length: 0,
  16549. /**
  16550. * Constructor for the collection.
  16551. *
  16552. * @constructor
  16553. * @method init
  16554. * @param {Array} items Optional array with items to add.
  16555. */
  16556. init: function(items) {
  16557. if (items) {
  16558. this.add(items);
  16559. }
  16560. },
  16561. /**
  16562. * Adds new items to the control collection.
  16563. *
  16564. * @method add
  16565. * @param {Array} items Array if items to add to collection.
  16566. * @return {tinymce.ui.Collection} Current collection instance.
  16567. */
  16568. add: function(items) {
  16569. var self = this;
  16570. // Force single item into array
  16571. if (!Tools.isArray(items)) {
  16572. if (items instanceof Collection) {
  16573. self.add(items.toArray());
  16574. } else {
  16575. push.call(self, items);
  16576. }
  16577. } else {
  16578. push.apply(self, items);
  16579. }
  16580. return self;
  16581. },
  16582. /**
  16583. * Sets the contents of the collection. This will remove any existing items
  16584. * and replace them with the ones specified in the input array.
  16585. *
  16586. * @method set
  16587. * @param {Array} items Array with items to set into the Collection.
  16588. * @return {tinymce.ui.Collection} Collection instance.
  16589. */
  16590. set: function(items) {
  16591. var self = this, len = self.length, i;
  16592. self.length = 0;
  16593. self.add(items);
  16594. // Remove old entries
  16595. for (i = self.length; i < len; i++) {
  16596. delete self[i];
  16597. }
  16598. return self;
  16599. },
  16600. /**
  16601. * Filters the collection item based on the specified selector expression or selector function.
  16602. *
  16603. * @method filter
  16604. * @param {String} selector Selector expression to filter items by.
  16605. * @return {tinymce.ui.Collection} Collection containing the filtered items.
  16606. */
  16607. filter: function(selector) {
  16608. var self = this, i, l, matches = [], item, match;
  16609. // Compile string into selector expression
  16610. if (typeof(selector) === "string") {
  16611. selector = new Selector(selector);
  16612. match = function(item) {
  16613. return selector.match(item);
  16614. };
  16615. } else {
  16616. // Use selector as matching function
  16617. match = selector;
  16618. }
  16619. for (i = 0, l = self.length; i < l; i++) {
  16620. item = self[i];
  16621. if (match(item)) {
  16622. matches.push(item);
  16623. }
  16624. }
  16625. return new Collection(matches);
  16626. },
  16627. /**
  16628. * Slices the items within the collection.
  16629. *
  16630. * @method slice
  16631. * @param {Number} index Index to slice at.
  16632. * @param {Number} len Optional length to slice.
  16633. * @return {tinymce.ui.Collection} Current collection.
  16634. */
  16635. slice: function() {
  16636. return new Collection(slice.apply(this, arguments));
  16637. },
  16638. /**
  16639. * Makes the current collection equal to the specified index.
  16640. *
  16641. * @method eq
  16642. * @param {Number} index Index of the item to set the collection to.
  16643. * @return {tinymce.ui.Collection} Current collection.
  16644. */
  16645. eq: function(index) {
  16646. return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  16647. },
  16648. /**
  16649. * Executes the specified callback on each item in collection.
  16650. *
  16651. * @method each
  16652. * @param {function} callback Callback to execute for each item in collection.
  16653. * @return {tinymce.ui.Collection} Current collection instance.
  16654. */
  16655. each: function(callback) {
  16656. Tools.each(this, callback);
  16657. return this;
  16658. },
  16659. /**
  16660. * Returns an JavaScript array object of the contents inside the collection.
  16661. *
  16662. * @method toArray
  16663. * @return {Array} Array with all items from collection.
  16664. */
  16665. toArray: function() {
  16666. return Tools.toArray(this);
  16667. },
  16668. /**
  16669. * Finds the index of the specified control or return -1 if it isn't in the collection.
  16670. *
  16671. * @method indexOf
  16672. * @param {Control} ctrl Control instance to look for.
  16673. * @return {Number} Index of the specified control or -1.
  16674. */
  16675. indexOf: function(ctrl) {
  16676. var self = this, i = self.length;
  16677. while (i--) {
  16678. if (self[i] === ctrl) {
  16679. break;
  16680. }
  16681. }
  16682. return i;
  16683. },
  16684. /**
  16685. * Returns a new collection of the contents in reverse order.
  16686. *
  16687. * @method reverse
  16688. * @return {tinymce.ui.Collection} Collection instance with reversed items.
  16689. */
  16690. reverse: function() {
  16691. return new Collection(Tools.toArray(this).reverse());
  16692. },
  16693. /**
  16694. * Returns true/false if the class exists or not.
  16695. *
  16696. * @method hasClass
  16697. * @param {String} cls Class to check for.
  16698. * @return {Boolean} true/false state if the class exists or not.
  16699. */
  16700. hasClass: function(cls) {
  16701. return this[0] ? this[0].hasClass(cls) : false;
  16702. },
  16703. /**
  16704. * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
  16705. *
  16706. * @method prop
  16707. * @param {String} name Property name to get/set.
  16708. * @param {Object} value Optional object value to set.
  16709. * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
  16710. */
  16711. prop: function(name, value) {
  16712. var self = this, undef, item;
  16713. if (value !== undef) {
  16714. self.each(function(item) {
  16715. if (item[name]) {
  16716. item[name](value);
  16717. }
  16718. });
  16719. return self;
  16720. }
  16721. item = self[0];
  16722. if (item && item[name]) {
  16723. return item[name]();
  16724. }
  16725. },
  16726. /**
  16727. * Executes the specific function name with optional arguments an all items in collection if it exists.
  16728. *
  16729. * @example collection.exec("myMethod", arg1, arg2, arg3);
  16730. * @method exec
  16731. * @param {String} name Name of the function to execute.
  16732. * @param {Object} ... Multiple arguments to pass to each function.
  16733. * @return {tinymce.ui.Collection} Current collection.
  16734. */
  16735. exec: function(name) {
  16736. var self = this, args = Tools.toArray(arguments).slice(1);
  16737. self.each(function(item) {
  16738. if (item[name]) {
  16739. item[name].apply(item, args);
  16740. }
  16741. });
  16742. return self;
  16743. },
  16744. /**
  16745. * Remove all items from collection and DOM.
  16746. *
  16747. * @method remove
  16748. * @return {tinymce.ui.Collection} Current collection.
  16749. */
  16750. remove: function() {
  16751. var i = this.length;
  16752. while (i--) {
  16753. this[i].remove();
  16754. }
  16755. return this;
  16756. }
  16757. /**
  16758. * Fires the specified event by name and arguments on the control. This will execute all
  16759. * bound event handlers.
  16760. *
  16761. * @method fire
  16762. * @param {String} name Name of the event to fire.
  16763. * @param {Object} args Optional arguments to pass to the event.
  16764. * @return {tinymce.ui.Collection} Current collection instance.
  16765. */
  16766. // fire: function(event, args) {}, -- Generated by code below
  16767. /**
  16768. * Binds a callback to the specified event. This event can both be
  16769. * native browser events like "click" or custom ones like PostRender.
  16770. *
  16771. * The callback function will have two parameters the first one being the control that received the event
  16772. * the second one will be the event object either the browsers native event object or a custom JS object.
  16773. *
  16774. * @method on
  16775. * @param {String} name Name of the event to bind. For example "click".
  16776. * @param {String/function} callback Callback function to execute ones the event occurs.
  16777. * @return {tinymce.ui.Collection} Current collection instance.
  16778. */
  16779. // on: function(name, callback) {}, -- Generated by code below
  16780. /**
  16781. * Unbinds the specified event and optionally a specific callback. If you omit the name
  16782. * parameter all event handlers will be removed. If you omit the callback all event handles
  16783. * by the specified name will be removed.
  16784. *
  16785. * @method off
  16786. * @param {String} name Optional name for the event to unbind.
  16787. * @param {function} callback Optional callback function to unbind.
  16788. * @return {tinymce.ui.Collection} Current collection instance.
  16789. */
  16790. // off: function(name, callback) {}, -- Generated by code below
  16791. /**
  16792. * Shows the items in the current collection.
  16793. *
  16794. * @method show
  16795. * @return {tinymce.ui.Collection} Current collection instance.
  16796. */
  16797. // show: function() {}, -- Generated by code below
  16798. /**
  16799. * Hides the items in the current collection.
  16800. *
  16801. * @method hide
  16802. * @return {tinymce.ui.Collection} Current collection instance.
  16803. */
  16804. // hide: function() {}, -- Generated by code below
  16805. /**
  16806. * Sets/gets the text contents of the items in the current collection.
  16807. *
  16808. * @method text
  16809. * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
  16810. */
  16811. // text: function(value) {}, -- Generated by code below
  16812. /**
  16813. * Sets/gets the name contents of the items in the current collection.
  16814. *
  16815. * @method name
  16816. * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
  16817. */
  16818. // name: function(value) {}, -- Generated by code below
  16819. /**
  16820. * Sets/gets the disabled state on the items in the current collection.
  16821. *
  16822. * @method disabled
  16823. * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
  16824. */
  16825. // disabled: function(state) {}, -- Generated by code below
  16826. /**
  16827. * Sets/gets the active state on the items in the current collection.
  16828. *
  16829. * @method active
  16830. * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
  16831. */
  16832. // active: function(state) {}, -- Generated by code below
  16833. /**
  16834. * Sets/gets the selected state on the items in the current collection.
  16835. *
  16836. * @method selected
  16837. * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
  16838. */
  16839. // selected: function(state) {}, -- Generated by code below
  16840. /**
  16841. * Sets/gets the selected state on the items in the current collection.
  16842. *
  16843. * @method visible
  16844. * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
  16845. */
  16846. // visible: function(state) {}, -- Generated by code below
  16847. /**
  16848. * Adds a class to all items in the collection.
  16849. *
  16850. * @method addClass
  16851. * @param {String} cls Class to add to each item.
  16852. * @return {tinymce.ui.Collection} Current collection instance.
  16853. */
  16854. // addClass: function(cls) {}, -- Generated by code below
  16855. /**
  16856. * Removes the specified class from all items in collection.
  16857. *
  16858. * @method removeClass
  16859. * @param {String} cls Class to remove from each item.
  16860. * @return {tinymce.ui.Collection} Current collection instance.
  16861. */
  16862. // removeClass: function(cls) {}, -- Generated by code below
  16863. };
  16864. // Extend tinymce.ui.Collection prototype with some generated control specific methods
  16865. Tools.each('fire on off show hide addClass removeClass append prepend before after reflow'.split(' '), function(name) {
  16866. proto[name] = function() {
  16867. var args = Tools.toArray(arguments);
  16868. this.each(function(ctrl) {
  16869. if (name in ctrl) {
  16870. ctrl[name].apply(ctrl, args);
  16871. }
  16872. });
  16873. return this;
  16874. };
  16875. });
  16876. // Extend tinymce.ui.Collection prototype with some property methods
  16877. Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
  16878. proto[name] = function(value) {
  16879. return this.prop(name, value);
  16880. };
  16881. });
  16882. // Create class based on the new prototype
  16883. Collection = Class.extend(proto);
  16884. // Stick Collection into Selector to prevent circual references
  16885. Selector.Collection = Collection;
  16886. return Collection;
  16887. });
  16888. // Included from: js/tinymce/classes/ui/DomUtils.js
  16889. /**
  16890. * DOMUtils.js
  16891. *
  16892. * Copyright, Moxiecode Systems AB
  16893. * Released under LGPL License.
  16894. *
  16895. * License: http://www.tinymce.com/license
  16896. * Contributing: http://www.tinymce.com/contributing
  16897. */
  16898. define("tinymce/ui/DomUtils", [
  16899. "tinymce/util/Tools",
  16900. "tinymce/dom/DOMUtils"
  16901. ], function(Tools, DOMUtils) {
  16902. "use strict";
  16903. return {
  16904. id: function() {
  16905. return DOMUtils.DOM.uniqueId();
  16906. },
  16907. createFragment: function(html) {
  16908. return DOMUtils.DOM.createFragment(html);
  16909. },
  16910. getWindowSize: function() {
  16911. return DOMUtils.DOM.getViewPort();
  16912. },
  16913. getSize: function(elm) {
  16914. var width, height;
  16915. if (elm.getBoundingClientRect) {
  16916. var rect = elm.getBoundingClientRect();
  16917. width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
  16918. height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
  16919. } else {
  16920. width = elm.offsetWidth;
  16921. height = elm.offsetHeight;
  16922. }
  16923. return {width: width, height: height};
  16924. },
  16925. getPos: function(elm, root) {
  16926. return DOMUtils.DOM.getPos(elm, root);
  16927. },
  16928. getViewPort: function(win) {
  16929. return DOMUtils.DOM.getViewPort(win);
  16930. },
  16931. get: function(id) {
  16932. return document.getElementById(id);
  16933. },
  16934. addClass : function(elm, cls) {
  16935. return DOMUtils.DOM.addClass(elm, cls);
  16936. },
  16937. removeClass : function(elm, cls) {
  16938. return DOMUtils.DOM.removeClass(elm, cls);
  16939. },
  16940. hasClass : function(elm, cls) {
  16941. return DOMUtils.DOM.hasClass(elm, cls);
  16942. },
  16943. toggleClass: function(elm, cls, state) {
  16944. return DOMUtils.DOM.toggleClass(elm, cls, state);
  16945. },
  16946. css: function(elm, name, value) {
  16947. return DOMUtils.DOM.setStyle(elm, name, value);
  16948. },
  16949. on: function(target, name, callback, scope) {
  16950. return DOMUtils.DOM.bind(target, name, callback, scope);
  16951. },
  16952. off: function(target, name, callback) {
  16953. return DOMUtils.DOM.unbind(target, name, callback);
  16954. },
  16955. fire: function(target, name, args) {
  16956. return DOMUtils.DOM.fire(target, name, args);
  16957. },
  16958. innerHtml: function(elm, html) {
  16959. // Workaround for <div> in <p> bug on IE 8 #6178
  16960. DOMUtils.DOM.setHTML(elm, html);
  16961. }
  16962. };
  16963. });
  16964. // Included from: js/tinymce/classes/ui/Control.js
  16965. /**
  16966. * Control.js
  16967. *
  16968. * Copyright, Moxiecode Systems AB
  16969. * Released under LGPL License.
  16970. *
  16971. * License: http://www.tinymce.com/license
  16972. * Contributing: http://www.tinymce.com/contributing
  16973. */
  16974. /*eslint consistent-this:0 */
  16975. /**
  16976. * This is the base class for all controls and containers. All UI control instances inherit
  16977. * from this one as it has the base logic needed by all of them.
  16978. *
  16979. * @class tinymce.ui.Control
  16980. */
  16981. define("tinymce/ui/Control", [
  16982. "tinymce/util/Class",
  16983. "tinymce/util/Tools",
  16984. "tinymce/ui/Collection",
  16985. "tinymce/ui/DomUtils"
  16986. ], function(Class, Tools, Collection, DomUtils) {
  16987. "use strict";
  16988. var nativeEvents = Tools.makeMap("focusin focusout scroll click dblclick mousedown mouseup mousemove mouseover" +
  16989. " mouseout mouseenter mouseleave wheel keydown keypress keyup contextmenu", " ");
  16990. var elementIdCache = {};
  16991. var hasMouseWheelEventSupport = "onmousewheel" in document;
  16992. var hasWheelEventSupport = false;
  16993. var Control = Class.extend({
  16994. Statics: {
  16995. elementIdCache: elementIdCache
  16996. },
  16997. isRtl: function() {
  16998. return Control.rtl;
  16999. },
  17000. /**
  17001. * Class/id prefix to use for all controls.
  17002. *
  17003. * @final
  17004. * @field {String} classPrefix
  17005. */
  17006. classPrefix: "mce-",
  17007. /**
  17008. * Constructs a new control instance with the specified settings.
  17009. *
  17010. * @constructor
  17011. * @param {Object} settings Name/value object with settings.
  17012. * @setting {String} style Style CSS properties to add.
  17013. * @setting {String} border Border box values example: 1 1 1 1
  17014. * @setting {String} padding Padding box values example: 1 1 1 1
  17015. * @setting {String} margin Margin box values example: 1 1 1 1
  17016. * @setting {Number} minWidth Minimal width for the control.
  17017. * @setting {Number} minHeight Minimal height for the control.
  17018. * @setting {String} classes Space separated list of classes to add.
  17019. * @setting {String} role WAI-ARIA role to use for control.
  17020. * @setting {Boolean} hidden Is the control hidden by default.
  17021. * @setting {Boolean} disabled Is the control disabled by default.
  17022. * @setting {String} name Name of the control instance.
  17023. */
  17024. init: function(settings) {
  17025. var self = this, classes, i;
  17026. self.settings = settings = Tools.extend({}, self.Defaults, settings);
  17027. // Initial states
  17028. self._id = settings.id || DomUtils.id();
  17029. self._text = self._name = '';
  17030. self._width = self._height = 0;
  17031. self._aria = {role: settings.role};
  17032. // Setup classes
  17033. classes = settings.classes;
  17034. if (classes) {
  17035. classes = classes.split(' ');
  17036. classes.map = {};
  17037. i = classes.length;
  17038. while (i--) {
  17039. classes.map[classes[i]] = true;
  17040. }
  17041. }
  17042. self._classes = classes || [];
  17043. self.visible(true);
  17044. // Set some properties
  17045. Tools.each('title text width height name classes visible disabled active value'.split(' '), function(name) {
  17046. var value = settings[name], undef;
  17047. if (value !== undef) {
  17048. self[name](value);
  17049. } else if (self['_' + name] === undef) {
  17050. self['_' + name] = false;
  17051. }
  17052. });
  17053. self.on('click', function() {
  17054. if (self.disabled()) {
  17055. return false;
  17056. }
  17057. });
  17058. // TODO: Is this needed duplicate code see above?
  17059. if (settings.classes) {
  17060. Tools.each(settings.classes.split(' '), function(cls) {
  17061. self.addClass(cls);
  17062. });
  17063. }
  17064. /**
  17065. * Name/value object with settings for the current control.
  17066. *
  17067. * @field {Object} settings
  17068. */
  17069. self.settings = settings;
  17070. self._borderBox = self.parseBox(settings.border);
  17071. self._paddingBox = self.parseBox(settings.padding);
  17072. self._marginBox = self.parseBox(settings.margin);
  17073. if (settings.hidden) {
  17074. self.hide();
  17075. }
  17076. },
  17077. // Will generate getter/setter methods for these properties
  17078. Properties: 'parent,title,text,width,height,disabled,active,name,value',
  17079. // Will generate empty dummy functions for these
  17080. Methods: 'renderHtml',
  17081. /**
  17082. * Returns the root element to render controls into.
  17083. *
  17084. * @method getContainerElm
  17085. * @return {Element} HTML DOM element to render into.
  17086. */
  17087. getContainerElm: function() {
  17088. return document.body;
  17089. },
  17090. /**
  17091. * Returns a control instance for the current DOM element.
  17092. *
  17093. * @method getParentCtrl
  17094. * @param {Element} elm HTML dom element to get parent control from.
  17095. * @return {tinymce.ui.Control} Control instance or undefined.
  17096. */
  17097. getParentCtrl: function(elm) {
  17098. var ctrl, lookup = this.getRoot().controlIdLookup;
  17099. while (elm && lookup) {
  17100. ctrl = lookup[elm.id];
  17101. if (ctrl) {
  17102. break;
  17103. }
  17104. elm = elm.parentNode;
  17105. }
  17106. return ctrl;
  17107. },
  17108. /**
  17109. * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
  17110. *
  17111. * @method parseBox
  17112. * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
  17113. * @return {Object} Object with top/right/bottom/left properties.
  17114. * @private
  17115. */
  17116. parseBox: function(value) {
  17117. var len, radix = 10;
  17118. if (!value) {
  17119. return;
  17120. }
  17121. if (typeof(value) === "number") {
  17122. value = value || 0;
  17123. return {
  17124. top: value,
  17125. left: value,
  17126. bottom: value,
  17127. right: value
  17128. };
  17129. }
  17130. value = value.split(' ');
  17131. len = value.length;
  17132. if (len === 1) {
  17133. value[1] = value[2] = value[3] = value[0];
  17134. } else if (len === 2) {
  17135. value[2] = value[0];
  17136. value[3] = value[1];
  17137. } else if (len === 3) {
  17138. value[3] = value[1];
  17139. }
  17140. return {
  17141. top: parseInt(value[0], radix) || 0,
  17142. right: parseInt(value[1], radix) || 0,
  17143. bottom: parseInt(value[2], radix) || 0,
  17144. left: parseInt(value[3], radix) || 0
  17145. };
  17146. },
  17147. borderBox: function() {
  17148. return this._borderBox;
  17149. },
  17150. paddingBox: function() {
  17151. return this._paddingBox;
  17152. },
  17153. marginBox: function() {
  17154. return this._marginBox;
  17155. },
  17156. measureBox: function(elm, prefix) {
  17157. function getStyle(name) {
  17158. var defaultView = document.defaultView;
  17159. if (defaultView) {
  17160. // Remove camelcase
  17161. name = name.replace(/[A-Z]/g, function(a) {
  17162. return '-' + a;
  17163. });
  17164. return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
  17165. }
  17166. return elm.currentStyle[name];
  17167. }
  17168. function getSide(name) {
  17169. var val = parseFloat(getStyle(name), 10);
  17170. return isNaN(val) ? 0 : val;
  17171. }
  17172. return {
  17173. top: getSide(prefix + "TopWidth"),
  17174. right: getSide(prefix + "RightWidth"),
  17175. bottom: getSide(prefix + "BottomWidth"),
  17176. left: getSide(prefix + "LeftWidth")
  17177. };
  17178. },
  17179. /**
  17180. * Initializes the current controls layout rect.
  17181. * This will be executed by the layout managers to determine the
  17182. * default minWidth/minHeight etc.
  17183. *
  17184. * @method initLayoutRect
  17185. * @return {Object} Layout rect instance.
  17186. */
  17187. initLayoutRect: function() {
  17188. var self = this, settings = self.settings, borderBox, layoutRect;
  17189. var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
  17190. var startMinWidth, startMinHeight, initialSize;
  17191. // Measure the current element
  17192. borderBox = self._borderBox = self._borderBox || self.measureBox(elm, 'border');
  17193. self._paddingBox = self._paddingBox || self.measureBox(elm, 'padding');
  17194. self._marginBox = self._marginBox || self.measureBox(elm, 'margin');
  17195. initialSize = DomUtils.getSize(elm);
  17196. // Setup minWidth/minHeight and width/height
  17197. startMinWidth = settings.minWidth;
  17198. startMinHeight = settings.minHeight;
  17199. minWidth = startMinWidth || initialSize.width;
  17200. minHeight = startMinHeight || initialSize.height;
  17201. width = settings.width;
  17202. height = settings.height;
  17203. autoResize = settings.autoResize;
  17204. autoResize = typeof(autoResize) != "undefined" ? autoResize : !width && !height;
  17205. width = width || minWidth;
  17206. height = height || minHeight;
  17207. var deltaW = borderBox.left + borderBox.right;
  17208. var deltaH = borderBox.top + borderBox.bottom;
  17209. var maxW = settings.maxWidth || 0xFFFF;
  17210. var maxH = settings.maxHeight || 0xFFFF;
  17211. // Setup initial layout rect
  17212. self._layoutRect = layoutRect = {
  17213. x: settings.x || 0,
  17214. y: settings.y || 0,
  17215. w: width,
  17216. h: height,
  17217. deltaW: deltaW,
  17218. deltaH: deltaH,
  17219. contentW: width - deltaW,
  17220. contentH: height - deltaH,
  17221. innerW: width - deltaW,
  17222. innerH: height - deltaH,
  17223. startMinWidth: startMinWidth || 0,
  17224. startMinHeight: startMinHeight || 0,
  17225. minW: Math.min(minWidth, maxW),
  17226. minH: Math.min(minHeight, maxH),
  17227. maxW: maxW,
  17228. maxH: maxH,
  17229. autoResize: autoResize,
  17230. scrollW: 0
  17231. };
  17232. self._lastLayoutRect = {};
  17233. return layoutRect;
  17234. },
  17235. /**
  17236. * Getter/setter for the current layout rect.
  17237. *
  17238. * @method layoutRect
  17239. * @param {Object} [newRect] Optional new layout rect.
  17240. * @return {tinymce.ui.Control/Object} Current control or rect object.
  17241. */
  17242. layoutRect: function(newRect) {
  17243. var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
  17244. // Initialize default layout rect
  17245. if (!curRect) {
  17246. curRect = self.initLayoutRect();
  17247. }
  17248. // Set new rect values
  17249. if (newRect) {
  17250. // Calc deltas between inner and outer sizes
  17251. deltaWidth = curRect.deltaW;
  17252. deltaHeight = curRect.deltaH;
  17253. // Set x position
  17254. if (newRect.x !== undef) {
  17255. curRect.x = newRect.x;
  17256. }
  17257. // Set y position
  17258. if (newRect.y !== undef) {
  17259. curRect.y = newRect.y;
  17260. }
  17261. // Set minW
  17262. if (newRect.minW !== undef) {
  17263. curRect.minW = newRect.minW;
  17264. }
  17265. // Set minH
  17266. if (newRect.minH !== undef) {
  17267. curRect.minH = newRect.minH;
  17268. }
  17269. // Set new width and calculate inner width
  17270. size = newRect.w;
  17271. if (size !== undef) {
  17272. size = size < curRect.minW ? curRect.minW : size;
  17273. size = size > curRect.maxW ? curRect.maxW : size;
  17274. curRect.w = size;
  17275. curRect.innerW = size - deltaWidth;
  17276. }
  17277. // Set new height and calculate inner height
  17278. size = newRect.h;
  17279. if (size !== undef) {
  17280. size = size < curRect.minH ? curRect.minH : size;
  17281. size = size > curRect.maxH ? curRect.maxH : size;
  17282. curRect.h = size;
  17283. curRect.innerH = size - deltaHeight;
  17284. }
  17285. // Set new inner width and calculate width
  17286. size = newRect.innerW;
  17287. if (size !== undef) {
  17288. size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
  17289. size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
  17290. curRect.innerW = size;
  17291. curRect.w = size + deltaWidth;
  17292. }
  17293. // Set new height and calculate inner height
  17294. size = newRect.innerH;
  17295. if (size !== undef) {
  17296. size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
  17297. size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
  17298. curRect.innerH = size;
  17299. curRect.h = size + deltaHeight;
  17300. }
  17301. // Set new contentW
  17302. if (newRect.contentW !== undef) {
  17303. curRect.contentW = newRect.contentW;
  17304. }
  17305. // Set new contentH
  17306. if (newRect.contentH !== undef) {
  17307. curRect.contentH = newRect.contentH;
  17308. }
  17309. // Compare last layout rect with the current one to see if we need to repaint or not
  17310. lastLayoutRect = self._lastLayoutRect;
  17311. if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
  17312. lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
  17313. repaintControls = Control.repaintControls;
  17314. if (repaintControls) {
  17315. if (repaintControls.map && !repaintControls.map[self._id]) {
  17316. repaintControls.push(self);
  17317. repaintControls.map[self._id] = true;
  17318. }
  17319. }
  17320. lastLayoutRect.x = curRect.x;
  17321. lastLayoutRect.y = curRect.y;
  17322. lastLayoutRect.w = curRect.w;
  17323. lastLayoutRect.h = curRect.h;
  17324. }
  17325. return self;
  17326. }
  17327. return curRect;
  17328. },
  17329. /**
  17330. * Repaints the control after a layout operation.
  17331. *
  17332. * @method repaint
  17333. */
  17334. repaint: function() {
  17335. var self = this, style, bodyStyle, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect, round;
  17336. // Use Math.round on all values on IE < 9
  17337. round = !document.createRange ? Math.round : function(value) {
  17338. return value;
  17339. };
  17340. style = self.getEl().style;
  17341. rect = self._layoutRect;
  17342. lastRepaintRect = self._lastRepaintRect || {};
  17343. borderBox = self._borderBox;
  17344. borderW = borderBox.left + borderBox.right;
  17345. borderH = borderBox.top + borderBox.bottom;
  17346. if (rect.x !== lastRepaintRect.x) {
  17347. style.left = round(rect.x) + 'px';
  17348. lastRepaintRect.x = rect.x;
  17349. }
  17350. if (rect.y !== lastRepaintRect.y) {
  17351. style.top = round(rect.y) + 'px';
  17352. lastRepaintRect.y = rect.y;
  17353. }
  17354. if (rect.w !== lastRepaintRect.w) {
  17355. style.width = round(rect.w - borderW) + 'px';
  17356. lastRepaintRect.w = rect.w;
  17357. }
  17358. if (rect.h !== lastRepaintRect.h) {
  17359. style.height = round(rect.h - borderH) + 'px';
  17360. lastRepaintRect.h = rect.h;
  17361. }
  17362. // Update body if needed
  17363. if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
  17364. bodyStyle = self.getEl('body').style;
  17365. bodyStyle.width = round(rect.innerW) + 'px';
  17366. lastRepaintRect.innerW = rect.innerW;
  17367. }
  17368. if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
  17369. bodyStyle = bodyStyle || self.getEl('body').style;
  17370. bodyStyle.height = round(rect.innerH) + 'px';
  17371. lastRepaintRect.innerH = rect.innerH;
  17372. }
  17373. self._lastRepaintRect = lastRepaintRect;
  17374. self.fire('repaint', {}, false);
  17375. },
  17376. /**
  17377. * Binds a callback to the specified event. This event can both be
  17378. * native browser events like "click" or custom ones like PostRender.
  17379. *
  17380. * The callback function will be passed a DOM event like object that enables yout do stop propagation.
  17381. *
  17382. * @method on
  17383. * @param {String} name Name of the event to bind. For example "click".
  17384. * @param {String/function} callback Callback function to execute ones the event occurs.
  17385. * @return {tinymce.ui.Control} Current control object.
  17386. */
  17387. on: function(name, callback) {
  17388. var self = this, bindings, handlers, names, i;
  17389. function resolveCallbackName(name) {
  17390. var callback, scope;
  17391. return function(e) {
  17392. if (!callback) {
  17393. self.parentsAndSelf().each(function(ctrl) {
  17394. var callbacks = ctrl.settings.callbacks;
  17395. if (callbacks && (callback = callbacks[name])) {
  17396. scope = ctrl;
  17397. return false;
  17398. }
  17399. });
  17400. }
  17401. return callback.call(scope, e);
  17402. };
  17403. }
  17404. if (callback) {
  17405. if (typeof(callback) == 'string') {
  17406. callback = resolveCallbackName(callback);
  17407. }
  17408. names = name.toLowerCase().split(' ');
  17409. i = names.length;
  17410. while (i--) {
  17411. name = names[i];
  17412. bindings = self._bindings;
  17413. if (!bindings) {
  17414. bindings = self._bindings = {};
  17415. }
  17416. handlers = bindings[name];
  17417. if (!handlers) {
  17418. handlers = bindings[name] = [];
  17419. }
  17420. handlers.push(callback);
  17421. if (nativeEvents[name]) {
  17422. if (!self._nativeEvents) {
  17423. self._nativeEvents = {};
  17424. }
  17425. self._nativeEvents[name] = true;
  17426. if (self._rendered) {
  17427. self.bindPendingEvents();
  17428. }
  17429. }
  17430. }
  17431. }
  17432. return self;
  17433. },
  17434. /**
  17435. * Unbinds the specified event and optionally a specific callback. If you omit the name
  17436. * parameter all event handlers will be removed. If you omit the callback all event handles
  17437. * by the specified name will be removed.
  17438. *
  17439. * @method off
  17440. * @param {String} [name] Name for the event to unbind.
  17441. * @param {function} [callback] Callback function to unbind.
  17442. * @return {mxex.ui.Control} Current control object.
  17443. */
  17444. off: function(name, callback) {
  17445. var self = this, i, bindings = self._bindings, handlers, bindingName, names, hi;
  17446. if (bindings) {
  17447. if (name) {
  17448. names = name.toLowerCase().split(' ');
  17449. i = names.length;
  17450. while (i--) {
  17451. name = names[i];
  17452. handlers = bindings[name];
  17453. // Unbind all handlers
  17454. if (!name) {
  17455. for (bindingName in bindings) {
  17456. bindings[bindingName].length = 0;
  17457. }
  17458. return self;
  17459. }
  17460. if (handlers) {
  17461. // Unbind all by name
  17462. if (!callback) {
  17463. handlers.length = 0;
  17464. } else {
  17465. // Unbind specific ones
  17466. hi = handlers.length;
  17467. while (hi--) {
  17468. if (handlers[hi] === callback) {
  17469. handlers.splice(hi, 1);
  17470. }
  17471. }
  17472. }
  17473. }
  17474. }
  17475. } else {
  17476. self._bindings = [];
  17477. }
  17478. }
  17479. return self;
  17480. },
  17481. /**
  17482. * Fires the specified event by name and arguments on the control. This will execute all
  17483. * bound event handlers.
  17484. *
  17485. * @method fire
  17486. * @param {String} name Name of the event to fire.
  17487. * @param {Object} [args] Arguments to pass to the event.
  17488. * @param {Boolean} [bubble] Value to control bubbeling. Defaults to true.
  17489. * @return {Object} Current arguments object.
  17490. */
  17491. fire: function(name, args, bubble) {
  17492. var self = this, i, l, handlers, parentCtrl;
  17493. name = name.toLowerCase();
  17494. // Dummy function that gets replaced on the delegation state functions
  17495. function returnFalse() {
  17496. return false;
  17497. }
  17498. // Dummy function that gets replaced on the delegation state functions
  17499. function returnTrue() {
  17500. return true;
  17501. }
  17502. // Setup empty object if args is omited
  17503. args = args || {};
  17504. // Stick type into event object
  17505. if (!args.type) {
  17506. args.type = name;
  17507. }
  17508. // Stick control into event
  17509. if (!args.control) {
  17510. args.control = self;
  17511. }
  17512. // Add event delegation methods if they are missing
  17513. if (!args.preventDefault) {
  17514. // Add preventDefault method
  17515. args.preventDefault = function() {
  17516. args.isDefaultPrevented = returnTrue;
  17517. };
  17518. // Add stopPropagation
  17519. args.stopPropagation = function() {
  17520. args.isPropagationStopped = returnTrue;
  17521. };
  17522. // Add stopImmediatePropagation
  17523. args.stopImmediatePropagation = function() {
  17524. args.isImmediatePropagationStopped = returnTrue;
  17525. };
  17526. // Add event delegation states
  17527. args.isDefaultPrevented = returnFalse;
  17528. args.isPropagationStopped = returnFalse;
  17529. args.isImmediatePropagationStopped = returnFalse;
  17530. }
  17531. if (self._bindings) {
  17532. handlers = self._bindings[name];
  17533. if (handlers) {
  17534. for (i = 0, l = handlers.length; i < l; i++) {
  17535. // Execute callback and break if the callback returns a false
  17536. if (!args.isImmediatePropagationStopped() && handlers[i].call(self, args) === false) {
  17537. break;
  17538. }
  17539. }
  17540. }
  17541. }
  17542. // Bubble event up to parent controls
  17543. if (bubble !== false) {
  17544. parentCtrl = self.parent();
  17545. while (parentCtrl && !args.isPropagationStopped()) {
  17546. parentCtrl.fire(name, args, false);
  17547. parentCtrl = parentCtrl.parent();
  17548. }
  17549. }
  17550. return args;
  17551. },
  17552. /**
  17553. * Returns true/false if the specified event has any listeners.
  17554. *
  17555. * @method hasEventListeners
  17556. * @param {String} name Name of the event to check for.
  17557. * @return {Boolean} True/false state if the event has listeners.
  17558. */
  17559. hasEventListeners: function(name) {
  17560. return name in this._bindings;
  17561. },
  17562. /**
  17563. * Returns a control collection with all parent controls.
  17564. *
  17565. * @method parents
  17566. * @param {String} selector Optional selector expression to find parents.
  17567. * @return {tinymce.ui.Collection} Collection with all parent controls.
  17568. */
  17569. parents: function(selector) {
  17570. var self = this, ctrl, parents = new Collection();
  17571. // Add each parent to collection
  17572. for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
  17573. parents.add(ctrl);
  17574. }
  17575. // Filter away everything that doesn't match the selector
  17576. if (selector) {
  17577. parents = parents.filter(selector);
  17578. }
  17579. return parents;
  17580. },
  17581. /**
  17582. * Returns the current control and it's parents.
  17583. *
  17584. * @method parentsAndSelf
  17585. * @param {String} selector Optional selector expression to find parents.
  17586. * @return {tinymce.ui.Collection} Collection with all parent controls.
  17587. */
  17588. parentsAndSelf: function(selector) {
  17589. return new Collection(this).add(this.parents(selector));
  17590. },
  17591. /**
  17592. * Returns the control next to the current control.
  17593. *
  17594. * @method next
  17595. * @return {tinymce.ui.Control} Next control instance.
  17596. */
  17597. next: function() {
  17598. var parentControls = this.parent().items();
  17599. return parentControls[parentControls.indexOf(this) + 1];
  17600. },
  17601. /**
  17602. * Returns the control previous to the current control.
  17603. *
  17604. * @method prev
  17605. * @return {tinymce.ui.Control} Previous control instance.
  17606. */
  17607. prev: function() {
  17608. var parentControls = this.parent().items();
  17609. return parentControls[parentControls.indexOf(this) - 1];
  17610. },
  17611. /**
  17612. * Find the common ancestor for two control instances.
  17613. *
  17614. * @method findCommonAncestor
  17615. * @param {tinymce.ui.Control} ctrl1 First control.
  17616. * @param {tinymce.ui.Control} ctrl2 Second control.
  17617. * @return {tinymce.ui.Control} Ancestor control instance.
  17618. */
  17619. findCommonAncestor: function(ctrl1, ctrl2) {
  17620. var parentCtrl;
  17621. while (ctrl1) {
  17622. parentCtrl = ctrl2;
  17623. while (parentCtrl && ctrl1 != parentCtrl) {
  17624. parentCtrl = parentCtrl.parent();
  17625. }
  17626. if (ctrl1 == parentCtrl) {
  17627. break;
  17628. }
  17629. ctrl1 = ctrl1.parent();
  17630. }
  17631. return ctrl1;
  17632. },
  17633. /**
  17634. * Returns true/false if the specific control has the specific class.
  17635. *
  17636. * @method hasClass
  17637. * @param {String} cls Class to check for.
  17638. * @param {String} [group] Sub element group name.
  17639. * @return {Boolean} True/false if the control has the specified class.
  17640. */
  17641. hasClass: function(cls, group) {
  17642. var classes = this._classes[group || 'control'];
  17643. cls = this.classPrefix + cls;
  17644. return classes && !!classes.map[cls];
  17645. },
  17646. /**
  17647. * Adds the specified class to the control
  17648. *
  17649. * @method addClass
  17650. * @param {String} cls Class to check for.
  17651. * @param {String} [group] Sub element group name.
  17652. * @return {tinymce.ui.Control} Current control object.
  17653. */
  17654. addClass: function(cls, group) {
  17655. var self = this, classes, elm;
  17656. cls = this.classPrefix + cls;
  17657. classes = self._classes[group || 'control'];
  17658. if (!classes) {
  17659. classes = [];
  17660. classes.map = {};
  17661. self._classes[group || 'control'] = classes;
  17662. }
  17663. if (!classes.map[cls]) {
  17664. classes.map[cls] = cls;
  17665. classes.push(cls);
  17666. if (self._rendered) {
  17667. elm = self.getEl(group);
  17668. if (elm) {
  17669. elm.className = classes.join(' ');
  17670. }
  17671. }
  17672. }
  17673. return self;
  17674. },
  17675. /**
  17676. * Removes the specified class from the control.
  17677. *
  17678. * @method removeClass
  17679. * @param {String} cls Class to remove.
  17680. * @param {String} [group] Sub element group name.
  17681. * @return {tinymce.ui.Control} Current control object.
  17682. */
  17683. removeClass: function(cls, group) {
  17684. var self = this, classes, i, elm;
  17685. cls = this.classPrefix + cls;
  17686. classes = self._classes[group || 'control'];
  17687. if (classes && classes.map[cls]) {
  17688. delete classes.map[cls];
  17689. i = classes.length;
  17690. while (i--) {
  17691. if (classes[i] === cls) {
  17692. classes.splice(i, 1);
  17693. }
  17694. }
  17695. }
  17696. if (self._rendered) {
  17697. elm = self.getEl(group);
  17698. if (elm) {
  17699. elm.className = classes.join(' ');
  17700. }
  17701. }
  17702. return self;
  17703. },
  17704. /**
  17705. * Toggles the specified class on the control.
  17706. *
  17707. * @method toggleClass
  17708. * @param {String} cls Class to remove.
  17709. * @param {Boolean} state True/false state to add/remove class.
  17710. * @param {String} [group] Sub element group name.
  17711. * @return {tinymce.ui.Control} Current control object.
  17712. */
  17713. toggleClass: function(cls, state, group) {
  17714. var self = this;
  17715. if (state) {
  17716. self.addClass(cls, group);
  17717. } else {
  17718. self.removeClass(cls, group);
  17719. }
  17720. return self;
  17721. },
  17722. /**
  17723. * Returns the class string for the specified group name.
  17724. *
  17725. * @method classes
  17726. * @param {String} [group] Group to get clases by.
  17727. * @return {String} Classes for the specified group.
  17728. */
  17729. classes: function(group) {
  17730. var classes = this._classes[group || 'control'];
  17731. return classes ? classes.join(' ') : '';
  17732. },
  17733. /**
  17734. * Sets the inner HTML of the control element.
  17735. *
  17736. * @method innerHtml
  17737. * @param {String} html Html string to set as inner html.
  17738. * @return {tinymce.ui.Control} Current control object.
  17739. */
  17740. innerHtml: function(html) {
  17741. DomUtils.innerHtml(this.getEl(), html);
  17742. return this;
  17743. },
  17744. /**
  17745. * Returns the control DOM element or sub element.
  17746. *
  17747. * @method getEl
  17748. * @param {String} [suffix] Suffix to get element by.
  17749. * @param {Boolean} [dropCache] True if the cache for the element should be dropped.
  17750. * @return {Element} HTML DOM element for the current control or it's children.
  17751. */
  17752. getEl: function(suffix, dropCache) {
  17753. var elm, id = suffix ? this._id + '-' + suffix : this._id;
  17754. elm = elementIdCache[id] = (dropCache === true ? null : elementIdCache[id]) || DomUtils.get(id);
  17755. return elm;
  17756. },
  17757. /**
  17758. * Sets/gets the visible for the control.
  17759. *
  17760. * @method visible
  17761. * @param {Boolean} state Value to set to control.
  17762. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  17763. */
  17764. visible: function(state) {
  17765. var self = this, parentCtrl;
  17766. if (typeof(state) !== "undefined") {
  17767. if (self._visible !== state) {
  17768. if (self._rendered) {
  17769. self.getEl().style.display = state ? '' : 'none';
  17770. }
  17771. self._visible = state;
  17772. // Parent container needs to reflow
  17773. parentCtrl = self.parent();
  17774. if (parentCtrl) {
  17775. parentCtrl._lastRect = null;
  17776. }
  17777. self.fire(state ? 'show' : 'hide');
  17778. }
  17779. return self;
  17780. }
  17781. return self._visible;
  17782. },
  17783. /**
  17784. * Sets the visible state to true.
  17785. *
  17786. * @method show
  17787. * @return {tinymce.ui.Control} Current control instance.
  17788. */
  17789. show: function() {
  17790. return this.visible(true);
  17791. },
  17792. /**
  17793. * Sets the visible state to false.
  17794. *
  17795. * @method hide
  17796. * @return {tinymce.ui.Control} Current control instance.
  17797. */
  17798. hide: function() {
  17799. return this.visible(false);
  17800. },
  17801. /**
  17802. * Focuses the current control.
  17803. *
  17804. * @method focus
  17805. * @return {tinymce.ui.Control} Current control instance.
  17806. */
  17807. focus: function() {
  17808. try {
  17809. this.getEl().focus();
  17810. } catch (ex) {
  17811. // Ignore IE error
  17812. }
  17813. return this;
  17814. },
  17815. /**
  17816. * Blurs the current control.
  17817. *
  17818. * @method blur
  17819. * @return {tinymce.ui.Control} Current control instance.
  17820. */
  17821. blur: function() {
  17822. this.getEl().blur();
  17823. return this;
  17824. },
  17825. /**
  17826. * Sets the specified aria property.
  17827. *
  17828. * @method aria
  17829. * @param {String} name Name of the aria property to set.
  17830. * @param {String} value Value of the aria property.
  17831. * @return {tinymce.ui.Control} Current control instance.
  17832. */
  17833. aria: function(name, value) {
  17834. var self = this, elm = self.getEl(self.ariaTarget);
  17835. if (typeof(value) === "undefined") {
  17836. return self._aria[name];
  17837. } else {
  17838. self._aria[name] = value;
  17839. }
  17840. if (self._rendered) {
  17841. elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
  17842. }
  17843. return self;
  17844. },
  17845. /**
  17846. * Encodes the specified string with HTML entities. It will also
  17847. * translate the string to different languages.
  17848. *
  17849. * @method encode
  17850. * @param {String/Object/Array} text Text to entity encode.
  17851. * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
  17852. * @return {String} Encoded and possible traslated string.
  17853. */
  17854. encode: function(text, translate) {
  17855. if (translate !== false) {
  17856. text = this.translate(text);
  17857. }
  17858. return (text || '').replace(/[&<>"]/g, function(match) {
  17859. return '&#' + match.charCodeAt(0) + ';';
  17860. });
  17861. },
  17862. /**
  17863. * Returns the translated string.
  17864. *
  17865. * @method translate
  17866. * @param {String} text Text to translate.
  17867. * @return {String} Translated string or the same as the input.
  17868. */
  17869. translate: function(text) {
  17870. return Control.translate ? Control.translate(text) : text;
  17871. },
  17872. /**
  17873. * Adds items before the current control.
  17874. *
  17875. * @method before
  17876. * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
  17877. * @return {tinymce.ui.Control} Current control instance.
  17878. */
  17879. before: function(items) {
  17880. var self = this, parent = self.parent();
  17881. if (parent) {
  17882. parent.insert(items, parent.items().indexOf(self), true);
  17883. }
  17884. return self;
  17885. },
  17886. /**
  17887. * Adds items after the current control.
  17888. *
  17889. * @method after
  17890. * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
  17891. * @return {tinymce.ui.Control} Current control instance.
  17892. */
  17893. after: function(items) {
  17894. var self = this, parent = self.parent();
  17895. if (parent) {
  17896. parent.insert(items, parent.items().indexOf(self));
  17897. }
  17898. return self;
  17899. },
  17900. /**
  17901. * Removes the current control from DOM and from UI collections.
  17902. *
  17903. * @method remove
  17904. * @return {tinymce.ui.Control} Current control instance.
  17905. */
  17906. remove: function() {
  17907. var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
  17908. if (self.items) {
  17909. var controls = self.items().toArray();
  17910. i = controls.length;
  17911. while (i--) {
  17912. controls[i].remove();
  17913. }
  17914. }
  17915. if (parent && parent.items) {
  17916. newItems = [];
  17917. parent.items().each(function(item) {
  17918. if (item !== self) {
  17919. newItems.push(item);
  17920. }
  17921. });
  17922. parent.items().set(newItems);
  17923. parent._lastRect = null;
  17924. }
  17925. if (self._eventsRoot && self._eventsRoot == self) {
  17926. DomUtils.off(elm);
  17927. }
  17928. var lookup = self.getRoot().controlIdLookup;
  17929. if (lookup) {
  17930. delete lookup[self._id];
  17931. }
  17932. delete elementIdCache[self._id];
  17933. if (elm && elm.parentNode) {
  17934. var nodes = elm.getElementsByTagName('*');
  17935. i = nodes.length;
  17936. while (i--) {
  17937. delete elementIdCache[nodes[i].id];
  17938. }
  17939. elm.parentNode.removeChild(elm);
  17940. }
  17941. self._rendered = false;
  17942. return self;
  17943. },
  17944. /**
  17945. * Renders the control before the specified element.
  17946. *
  17947. * @method renderBefore
  17948. * @param {Element} elm Element to render before.
  17949. * @return {tinymce.ui.Control} Current control instance.
  17950. */
  17951. renderBefore: function(elm) {
  17952. var self = this;
  17953. elm.parentNode.insertBefore(DomUtils.createFragment(self.renderHtml()), elm);
  17954. self.postRender();
  17955. return self;
  17956. },
  17957. /**
  17958. * Renders the control to the specified element.
  17959. *
  17960. * @method renderBefore
  17961. * @param {Element} elm Element to render to.
  17962. * @return {tinymce.ui.Control} Current control instance.
  17963. */
  17964. renderTo: function(elm) {
  17965. var self = this;
  17966. elm = elm || self.getContainerElm();
  17967. elm.appendChild(DomUtils.createFragment(self.renderHtml()));
  17968. self.postRender();
  17969. return self;
  17970. },
  17971. /**
  17972. * Post render method. Called after the control has been rendered to the target.
  17973. *
  17974. * @method postRender
  17975. * @return {tinymce.ui.Control} Current control instance.
  17976. */
  17977. postRender: function() {
  17978. var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
  17979. // Bind on<event> settings
  17980. for (name in settings) {
  17981. if (name.indexOf("on") === 0) {
  17982. self.on(name.substr(2), settings[name]);
  17983. }
  17984. }
  17985. if (self._eventsRoot) {
  17986. for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
  17987. parentEventsRoot = parent._eventsRoot;
  17988. }
  17989. if (parentEventsRoot) {
  17990. for (name in parentEventsRoot._nativeEvents) {
  17991. self._nativeEvents[name] = true;
  17992. }
  17993. }
  17994. }
  17995. self.bindPendingEvents();
  17996. if (settings.style) {
  17997. elm = self.getEl();
  17998. if (elm) {
  17999. elm.setAttribute('style', settings.style);
  18000. elm.style.cssText = settings.style;
  18001. }
  18002. }
  18003. if (!self._visible) {
  18004. DomUtils.css(self.getEl(), 'display', 'none');
  18005. }
  18006. if (self.settings.border) {
  18007. box = self.borderBox();
  18008. DomUtils.css(self.getEl(), {
  18009. 'border-top-width': box.top,
  18010. 'border-right-width': box.right,
  18011. 'border-bottom-width': box.bottom,
  18012. 'border-left-width': box.left
  18013. });
  18014. }
  18015. // Add instance to lookup
  18016. var root = self.getRoot();
  18017. if (!root.controlIdLookup) {
  18018. root.controlIdLookup = {};
  18019. }
  18020. root.controlIdLookup[self._id] = self;
  18021. for (var key in self._aria) {
  18022. self.aria(key, self._aria[key]);
  18023. }
  18024. self.fire('postrender', {}, false);
  18025. },
  18026. /**
  18027. * Scrolls the current control into view.
  18028. *
  18029. * @method scrollIntoView
  18030. * @param {String} align Alignment in view top|center|bottom.
  18031. * @return {tinymce.ui.Control} Current control instance.
  18032. */
  18033. scrollIntoView: function(align) {
  18034. function getOffset(elm, rootElm) {
  18035. var x, y, parent = elm;
  18036. x = y = 0;
  18037. while (parent && parent != rootElm && parent.nodeType) {
  18038. x += parent.offsetLeft || 0;
  18039. y += parent.offsetTop || 0;
  18040. parent = parent.offsetParent;
  18041. }
  18042. return {x: x, y: y};
  18043. }
  18044. var elm = this.getEl(), parentElm = elm.parentNode;
  18045. var x, y, width, height, parentWidth, parentHeight;
  18046. var pos = getOffset(elm, parentElm);
  18047. x = pos.x;
  18048. y = pos.y;
  18049. width = elm.offsetWidth;
  18050. height = elm.offsetHeight;
  18051. parentWidth = parentElm.clientWidth;
  18052. parentHeight = parentElm.clientHeight;
  18053. if (align == "end") {
  18054. x -= parentWidth - width;
  18055. y -= parentHeight - height;
  18056. } else if (align == "center") {
  18057. x -= (parentWidth / 2) - (width / 2);
  18058. y -= (parentHeight / 2) - (height / 2);
  18059. }
  18060. parentElm.scrollLeft = x;
  18061. parentElm.scrollTop = y;
  18062. return this;
  18063. },
  18064. /**
  18065. * Binds pending DOM events.
  18066. *
  18067. * @private
  18068. */
  18069. bindPendingEvents: function() {
  18070. var self = this, i, l, parents, eventRootCtrl, nativeEvents, name;
  18071. function delegate(e) {
  18072. var control = self.getParentCtrl(e.target);
  18073. if (control) {
  18074. control.fire(e.type, e);
  18075. }
  18076. }
  18077. function mouseLeaveHandler() {
  18078. var ctrl = eventRootCtrl._lastHoverCtrl;
  18079. if (ctrl) {
  18080. ctrl.fire("mouseleave", {target: ctrl.getEl()});
  18081. ctrl.parents().each(function(ctrl) {
  18082. ctrl.fire("mouseleave", {target: ctrl.getEl()});
  18083. });
  18084. eventRootCtrl._lastHoverCtrl = null;
  18085. }
  18086. }
  18087. function mouseEnterHandler(e) {
  18088. var ctrl = self.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
  18089. // Over on a new control
  18090. if (ctrl !== lastCtrl) {
  18091. eventRootCtrl._lastHoverCtrl = ctrl;
  18092. parents = ctrl.parents().toArray().reverse();
  18093. parents.push(ctrl);
  18094. if (lastCtrl) {
  18095. lastParents = lastCtrl.parents().toArray().reverse();
  18096. lastParents.push(lastCtrl);
  18097. for (idx = 0; idx < lastParents.length; idx++) {
  18098. if (parents[idx] !== lastParents[idx]) {
  18099. break;
  18100. }
  18101. }
  18102. for (i = lastParents.length - 1; i >= idx; i--) {
  18103. lastCtrl = lastParents[i];
  18104. lastCtrl.fire("mouseleave", {
  18105. target : lastCtrl.getEl()
  18106. });
  18107. }
  18108. }
  18109. for (i = idx; i < parents.length; i++) {
  18110. ctrl = parents[i];
  18111. ctrl.fire("mouseenter", {
  18112. target : ctrl.getEl()
  18113. });
  18114. }
  18115. }
  18116. }
  18117. function fixWheelEvent(e) {
  18118. e.preventDefault();
  18119. if (e.type == "mousewheel") {
  18120. e.deltaY = -1 / 40 * e.wheelDelta;
  18121. if (e.wheelDeltaX) {
  18122. e.deltaX = -1 / 40 * e.wheelDeltaX;
  18123. }
  18124. } else {
  18125. e.deltaX = 0;
  18126. e.deltaY = e.detail;
  18127. }
  18128. e = self.fire("wheel", e);
  18129. }
  18130. self._rendered = true;
  18131. nativeEvents = self._nativeEvents;
  18132. if (nativeEvents) {
  18133. // Find event root element if it exists
  18134. parents = self.parents().toArray();
  18135. parents.unshift(self);
  18136. for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
  18137. eventRootCtrl = parents[i]._eventsRoot;
  18138. }
  18139. // Event root wasn't found the use the root control
  18140. if (!eventRootCtrl) {
  18141. eventRootCtrl = parents[parents.length - 1] || self;
  18142. }
  18143. // Set the eventsRoot property on children that didn't have it
  18144. self._eventsRoot = eventRootCtrl;
  18145. for (l = i, i = 0; i < l; i++) {
  18146. parents[i]._eventsRoot = eventRootCtrl;
  18147. }
  18148. var eventRootDelegates = eventRootCtrl._delegates;
  18149. if (!eventRootDelegates) {
  18150. eventRootDelegates = eventRootCtrl._delegates = {};
  18151. }
  18152. // Bind native event delegates
  18153. for (name in nativeEvents) {
  18154. if (!nativeEvents) {
  18155. return false;
  18156. }
  18157. if (name === "wheel" && !hasWheelEventSupport) {
  18158. if (hasMouseWheelEventSupport) {
  18159. DomUtils.on(self.getEl(), "mousewheel", fixWheelEvent);
  18160. } else {
  18161. DomUtils.on(self.getEl(), "DOMMouseScroll", fixWheelEvent);
  18162. }
  18163. continue;
  18164. }
  18165. // Special treatment for mousenter/mouseleave since these doesn't bubble
  18166. if (name === "mouseenter" || name === "mouseleave") {
  18167. // Fake mousenter/mouseleave
  18168. if (!eventRootCtrl._hasMouseEnter) {
  18169. DomUtils.on(eventRootCtrl.getEl(), "mouseleave", mouseLeaveHandler);
  18170. DomUtils.on(eventRootCtrl.getEl(), "mouseover", mouseEnterHandler);
  18171. eventRootCtrl._hasMouseEnter = 1;
  18172. }
  18173. } else if (!eventRootDelegates[name]) {
  18174. DomUtils.on(eventRootCtrl.getEl(), name, delegate);
  18175. eventRootDelegates[name] = true;
  18176. }
  18177. // Remove the event once it's bound
  18178. nativeEvents[name] = false;
  18179. }
  18180. }
  18181. },
  18182. getRoot: function() {
  18183. var ctrl = this, rootControl, parents = [];
  18184. while (ctrl) {
  18185. if (ctrl.rootControl) {
  18186. rootControl = ctrl.rootControl;
  18187. break;
  18188. }
  18189. parents.push(ctrl);
  18190. rootControl = ctrl;
  18191. ctrl = ctrl.parent();
  18192. }
  18193. if (!rootControl) {
  18194. rootControl = this;
  18195. }
  18196. var i = parents.length;
  18197. while (i--) {
  18198. parents[i].rootControl = rootControl;
  18199. }
  18200. return rootControl;
  18201. },
  18202. /**
  18203. * Reflows the current control and it's parents.
  18204. * This should be used after you for example append children to the current control so
  18205. * that the layout managers know that they need to reposition everything.
  18206. *
  18207. * @example
  18208. * container.append({type: 'button', text: 'My button'}).reflow();
  18209. *
  18210. * @method reflow
  18211. * @return {tinymce.ui.Control} Current control instance.
  18212. */
  18213. reflow: function() {
  18214. this.repaint();
  18215. return this;
  18216. }
  18217. /**
  18218. * Sets/gets the parent container for the control.
  18219. *
  18220. * @method parent
  18221. * @param {tinymce.ui.Container} parent Optional parent to set.
  18222. * @return {tinymce.ui.Control} Parent control or the current control on a set action.
  18223. */
  18224. // parent: function(parent) {} -- Generated
  18225. /**
  18226. * Sets/gets the text for the control.
  18227. *
  18228. * @method text
  18229. * @param {String} value Value to set to control.
  18230. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18231. */
  18232. // text: function(value) {} -- Generated
  18233. /**
  18234. * Sets/gets the width for the control.
  18235. *
  18236. * @method width
  18237. * @param {Number} value Value to set to control.
  18238. * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18239. */
  18240. // width: function(value) {} -- Generated
  18241. /**
  18242. * Sets/gets the height for the control.
  18243. *
  18244. * @method height
  18245. * @param {Number} value Value to set to control.
  18246. * @return {Number/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18247. */
  18248. // height: function(value) {} -- Generated
  18249. /**
  18250. * Sets/gets the disabled state on the control.
  18251. *
  18252. * @method disabled
  18253. * @param {Boolean} state Value to set to control.
  18254. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  18255. */
  18256. // disabled: function(state) {} -- Generated
  18257. /**
  18258. * Sets/gets the active for the control.
  18259. *
  18260. * @method active
  18261. * @param {Boolean} state Value to set to control.
  18262. * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
  18263. */
  18264. // active: function(state) {} -- Generated
  18265. /**
  18266. * Sets/gets the name for the control.
  18267. *
  18268. * @method name
  18269. * @param {String} value Value to set to control.
  18270. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18271. */
  18272. // name: function(value) {} -- Generated
  18273. /**
  18274. * Sets/gets the title for the control.
  18275. *
  18276. * @method title
  18277. * @param {String} value Value to set to control.
  18278. * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
  18279. */
  18280. // title: function(value) {} -- Generated
  18281. });
  18282. return Control;
  18283. });
  18284. // Included from: js/tinymce/classes/ui/Factory.js
  18285. /**
  18286. * Factory.js
  18287. *
  18288. * Copyright, Moxiecode Systems AB
  18289. * Released under LGPL License.
  18290. *
  18291. * License: http://www.tinymce.com/license
  18292. * Contributing: http://www.tinymce.com/contributing
  18293. */
  18294. /*global tinymce:true */
  18295. /**
  18296. * This class is a factory for control instances. This enables you
  18297. * to create instances of controls without having to require the UI controls directly.
  18298. *
  18299. * It also allow you to override or add new control types.
  18300. *
  18301. * @class tinymce.ui.Factory
  18302. */
  18303. define("tinymce/ui/Factory", [], function() {
  18304. "use strict";
  18305. var types = {}, namespaceInit;
  18306. return {
  18307. /**
  18308. * Adds a new control instance type to the factory.
  18309. *
  18310. * @method add
  18311. * @param {String} type Type name for example "button".
  18312. * @param {function} typeClass Class type function.
  18313. */
  18314. add: function(type, typeClass) {
  18315. types[type.toLowerCase()] = typeClass;
  18316. },
  18317. /**
  18318. * Returns true/false if the specified type exists or not.
  18319. *
  18320. * @method has
  18321. * @param {String} type Type to look for.
  18322. * @return {Boolean} true/false if the control by name exists.
  18323. */
  18324. has: function(type) {
  18325. return !!types[type.toLowerCase()];
  18326. },
  18327. /**
  18328. * Creates a new control instance based on the settings provided. The instance created will be
  18329. * based on the specified type property it can also create whole structures of components out of
  18330. * the specified JSON object.
  18331. *
  18332. * @example
  18333. * tinymce.ui.Factory.create({
  18334. * type: 'button',
  18335. * text: 'Hello world!'
  18336. * });
  18337. *
  18338. * @method create
  18339. * @param {Object/String} settings Name/Value object with items used to create the type.
  18340. * @return {tinymce.ui.Control} Control instance based on the specified type.
  18341. */
  18342. create: function(type, settings) {
  18343. var ControlType, name, namespace;
  18344. // Build type lookup
  18345. if (!namespaceInit) {
  18346. namespace = tinymce.ui;
  18347. for (name in namespace) {
  18348. types[name.toLowerCase()] = namespace[name];
  18349. }
  18350. namespaceInit = true;
  18351. }
  18352. // If string is specified then use it as the type
  18353. if (typeof(type) == 'string') {
  18354. settings = settings || {};
  18355. settings.type = type;
  18356. } else {
  18357. settings = type;
  18358. type = settings.type;
  18359. }
  18360. // Find control type
  18361. type = type.toLowerCase();
  18362. ControlType = types[type];
  18363. // #if debug
  18364. if (!ControlType) {
  18365. throw new Error("Could not find control by type: " + type);
  18366. }
  18367. // #endif
  18368. ControlType = new ControlType(settings);
  18369. ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
  18370. return ControlType;
  18371. }
  18372. };
  18373. });
  18374. // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
  18375. /**
  18376. * KeyboardNavigation.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. /**
  18385. * This class handles keyboard navigation of controls and elements.
  18386. *
  18387. * @class tinymce.ui.KeyboardNavigation
  18388. */
  18389. define("tinymce/ui/KeyboardNavigation", [
  18390. ], function() {
  18391. "use strict";
  18392. /**
  18393. * This class handles all keyboard navigation for WAI-ARIA support. Each root container
  18394. * gets an instance of this class.
  18395. *
  18396. * @constructor
  18397. */
  18398. return function(settings) {
  18399. var root = settings.root, focusedElement, focusedControl;
  18400. focusedElement = document.activeElement;
  18401. focusedControl = root.getParentCtrl(focusedElement);
  18402. /**
  18403. * Returns the currently focused elements wai aria role of the currently
  18404. * focused element or specified element.
  18405. *
  18406. * @private
  18407. * @param {Element} elm Optional element to get role from.
  18408. * @return {String} Role of specified element.
  18409. */
  18410. function getRole(elm) {
  18411. elm = elm || focusedElement;
  18412. return elm && elm.getAttribute('role');
  18413. }
  18414. /**
  18415. * Returns the wai role of the parent element of the currently
  18416. * focused element or specified element.
  18417. *
  18418. * @private
  18419. * @param {Element} elm Optional element to get parent role from.
  18420. * @return {String} Role of the first parent that has a role.
  18421. */
  18422. function getParentRole(elm) {
  18423. var role, parent = elm || focusedElement;
  18424. while ((parent = parent.parentNode)) {
  18425. if ((role = getRole(parent))) {
  18426. return role;
  18427. }
  18428. }
  18429. }
  18430. /**
  18431. * Returns a wai aria property by name for example aria-selected.
  18432. *
  18433. * @private
  18434. * @param {String} name Name of the aria property to get for example "disabled".
  18435. * @return {String} Aria property value.
  18436. */
  18437. function getAriaProp(name) {
  18438. var elm = focusedElement;
  18439. if (elm) {
  18440. return elm.getAttribute('aria-' + name);
  18441. }
  18442. }
  18443. /**
  18444. * Is the element a text input element or not.
  18445. *
  18446. * @private
  18447. * @param {Element} elm Element to check if it's an text input element or not.
  18448. * @return {Boolean} True/false if the element is a text element or not.
  18449. */
  18450. function isTextInputElement(elm) {
  18451. var tagName = elm.tagName.toUpperCase();
  18452. // Notice: since type can be "email" etc we don't check the type
  18453. // So all input elements gets treated as text input elements
  18454. return tagName == "INPUT" || tagName == "TEXTAREA";
  18455. }
  18456. /**
  18457. * Returns true/false if the specified element can be focused or not.
  18458. *
  18459. * @private
  18460. * @param {Element} elm DOM element to check if it can be focused or not.
  18461. * @return {Boolean} True/false if the element can have focus.
  18462. */
  18463. function canFocus(elm) {
  18464. if (isTextInputElement(elm) && !elm.hidden) {
  18465. return true;
  18466. }
  18467. if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
  18468. return true;
  18469. }
  18470. return false;
  18471. }
  18472. /**
  18473. * Returns an array of focusable visible elements within the specified container element.
  18474. *
  18475. * @private
  18476. * @param {Element} elm DOM element to find focusable elements within.
  18477. * @return {Array} Array of focusable elements.
  18478. */
  18479. function getFocusElements(elm) {
  18480. var elements = [];
  18481. function collect(elm) {
  18482. if (elm.nodeType != 1 || elm.style.display == 'none') {
  18483. return;
  18484. }
  18485. if (canFocus(elm)) {
  18486. elements.push(elm);
  18487. }
  18488. for (var i = 0; i < elm.childNodes.length; i++) {
  18489. collect(elm.childNodes[i]);
  18490. }
  18491. }
  18492. collect(elm || root.getEl());
  18493. return elements;
  18494. }
  18495. /**
  18496. * Returns the navigation root control for the specified control. The navigation root
  18497. * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
  18498. * It will look for parents of the specified target control or the currenty focused control if this option is omitted.
  18499. *
  18500. * @private
  18501. * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
  18502. * @return {tinymce.ui.Control} Navigation root control.
  18503. */
  18504. function getNavigationRoot(targetControl) {
  18505. var navigationRoot, controls;
  18506. targetControl = targetControl || focusedControl;
  18507. controls = targetControl.parents().toArray();
  18508. controls.unshift(targetControl);
  18509. for (var i = 0; i < controls.length; i++) {
  18510. navigationRoot = controls[i];
  18511. if (navigationRoot.settings.ariaRoot) {
  18512. break;
  18513. }
  18514. }
  18515. return navigationRoot;
  18516. }
  18517. /**
  18518. * Focuses the first item in the specified targetControl element or the last aria index if the
  18519. * navigation root has the ariaRemember option enabled.
  18520. *
  18521. * @private
  18522. * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
  18523. */
  18524. function focusFirst(targetControl) {
  18525. var navigationRoot = getNavigationRoot(targetControl);
  18526. var focusElements = getFocusElements(navigationRoot.getEl());
  18527. if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
  18528. moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
  18529. } else {
  18530. moveFocusToIndex(0, focusElements);
  18531. }
  18532. }
  18533. /**
  18534. * Moves the focus to the specified index within the elements list.
  18535. * This will scope the index to the size of the element list if it changed.
  18536. *
  18537. * @private
  18538. * @param {Number} idx Specified index to move to.
  18539. * @param {Array} elements Array with dom elements to move focus within.
  18540. * @return {Number} Input index or a changed index if it was out of range.
  18541. */
  18542. function moveFocusToIndex(idx, elements) {
  18543. if (idx < 0) {
  18544. idx = elements.length - 1;
  18545. } else if (idx >= elements.length) {
  18546. idx = 0;
  18547. }
  18548. if (elements[idx]) {
  18549. elements[idx].focus();
  18550. }
  18551. return idx;
  18552. }
  18553. /**
  18554. * Moves the focus forwards or backwards.
  18555. *
  18556. * @private
  18557. * @param {Number} dir Direction to move in positive means forward, negative means backwards.
  18558. * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
  18559. */
  18560. function moveFocus(dir, elements) {
  18561. var idx = -1, navigationRoot = getNavigationRoot();
  18562. elements = elements || getFocusElements(navigationRoot.getEl());
  18563. for (var i = 0; i < elements.length; i++) {
  18564. if (elements[i] === focusedElement) {
  18565. idx = i;
  18566. }
  18567. }
  18568. idx += dir;
  18569. navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
  18570. }
  18571. /**
  18572. * Moves the focus to the left this is called by the left key.
  18573. *
  18574. * @private
  18575. */
  18576. function left() {
  18577. var parentRole = getParentRole();
  18578. if (parentRole == "tablist") {
  18579. moveFocus(-1, getFocusElements(focusedElement.parentNode));
  18580. } else if (focusedControl.parent().submenu) {
  18581. cancel();
  18582. } else {
  18583. moveFocus(-1);
  18584. }
  18585. }
  18586. /**
  18587. * Moves the focus to the right this is called by the right key.
  18588. *
  18589. * @private
  18590. */
  18591. function right() {
  18592. var role = getRole(), parentRole = getParentRole();
  18593. if (parentRole == "tablist") {
  18594. moveFocus(1, getFocusElements(focusedElement.parentNode));
  18595. } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
  18596. enter();
  18597. } else {
  18598. moveFocus(1);
  18599. }
  18600. }
  18601. /**
  18602. * Moves the focus to the up this is called by the up key.
  18603. *
  18604. * @private
  18605. */
  18606. function up() {
  18607. moveFocus(-1);
  18608. }
  18609. /**
  18610. * Moves the focus to the up this is called by the down key.
  18611. *
  18612. * @private
  18613. */
  18614. function down() {
  18615. var role = getRole(), parentRole = getParentRole();
  18616. if (role == "menuitem" && parentRole == "menubar") {
  18617. enter();
  18618. } else if (role == "button" && getAriaProp('haspopup')) {
  18619. enter({key: 'down'});
  18620. } else {
  18621. moveFocus(1);
  18622. }
  18623. }
  18624. /**
  18625. * Moves the focus to the next item or previous item depending on shift key.
  18626. *
  18627. * @private
  18628. * @param {DOMEvent} e DOM event object.
  18629. */
  18630. function tab(e) {
  18631. var parentRole = getParentRole();
  18632. if (parentRole == "tablist") {
  18633. var elm = getFocusElements(focusedControl.getEl('body'))[0];
  18634. if (elm) {
  18635. elm.focus();
  18636. }
  18637. } else {
  18638. moveFocus(e.shiftKey ? -1 : 1);
  18639. }
  18640. }
  18641. /**
  18642. * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
  18643. *
  18644. * @private
  18645. */
  18646. function cancel() {
  18647. focusedControl.fire('cancel');
  18648. }
  18649. /**
  18650. * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
  18651. *
  18652. * @private
  18653. * @param {Object} aria Optional aria data to pass along with the enter event.
  18654. */
  18655. function enter(aria) {
  18656. aria = aria || {};
  18657. focusedControl.fire('click', {target: focusedElement, aria: aria});
  18658. }
  18659. root.on('keydown', function(e) {
  18660. function handleNonTabOrEscEvent(e, handler) {
  18661. // Ignore non tab keys for text elements
  18662. if (isTextInputElement(focusedElement)) {
  18663. return;
  18664. }
  18665. if (handler(e) !== false) {
  18666. e.preventDefault();
  18667. }
  18668. }
  18669. if (e.isDefaultPrevented()) {
  18670. return;
  18671. }
  18672. switch (e.keyCode) {
  18673. case 37: // DOM_VK_LEFT
  18674. handleNonTabOrEscEvent(e, left);
  18675. break;
  18676. case 39: // DOM_VK_RIGHT
  18677. handleNonTabOrEscEvent(e, right);
  18678. break;
  18679. case 38: // DOM_VK_UP
  18680. handleNonTabOrEscEvent(e, up);
  18681. break;
  18682. case 40: // DOM_VK_DOWN
  18683. handleNonTabOrEscEvent(e, down);
  18684. break;
  18685. case 27: // DOM_VK_ESCAPE
  18686. cancel();
  18687. break;
  18688. case 14: // DOM_VK_ENTER
  18689. case 13: // DOM_VK_RETURN
  18690. case 32: // DOM_VK_SPACE
  18691. handleNonTabOrEscEvent(e, enter);
  18692. break;
  18693. case 9: // DOM_VK_TAB
  18694. if (tab(e) !== false) {
  18695. e.preventDefault();
  18696. }
  18697. break;
  18698. }
  18699. });
  18700. root.on('focusin', function(e) {
  18701. focusedElement = e.target;
  18702. focusedControl = e.control;
  18703. });
  18704. return {
  18705. focusFirst: focusFirst
  18706. };
  18707. };
  18708. });
  18709. // Included from: js/tinymce/classes/ui/Container.js
  18710. /**
  18711. * Container.js
  18712. *
  18713. * Copyright, Moxiecode Systems AB
  18714. * Released under LGPL License.
  18715. *
  18716. * License: http://www.tinymce.com/license
  18717. * Contributing: http://www.tinymce.com/contributing
  18718. */
  18719. /**
  18720. * Container control. This is extended by all controls that can have
  18721. * children such as panels etc. You can also use this class directly as an
  18722. * generic container instance. The container doesn't have any specific role or style.
  18723. *
  18724. * @-x-less Container.less
  18725. * @class tinymce.ui.Container
  18726. * @extends tinymce.ui.Control
  18727. */
  18728. define("tinymce/ui/Container", [
  18729. "tinymce/ui/Control",
  18730. "tinymce/ui/Collection",
  18731. "tinymce/ui/Selector",
  18732. "tinymce/ui/Factory",
  18733. "tinymce/ui/KeyboardNavigation",
  18734. "tinymce/util/Tools",
  18735. "tinymce/ui/DomUtils"
  18736. ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
  18737. "use strict";
  18738. var selectorCache = {};
  18739. return Control.extend({
  18740. layout: '',
  18741. innerClass: 'container-inner',
  18742. /**
  18743. * Constructs a new control instance with the specified settings.
  18744. *
  18745. * @constructor
  18746. * @param {Object} settings Name/value object with settings.
  18747. * @setting {Array} items Items to add to container in JSON format or control instances.
  18748. * @setting {String} layout Layout manager by name to use.
  18749. * @setting {Object} defaults Default settings to apply to all items.
  18750. */
  18751. init: function(settings) {
  18752. var self = this;
  18753. self._super(settings);
  18754. settings = self.settings;
  18755. self._fixed = settings.fixed;
  18756. self._items = new Collection();
  18757. if (self.isRtl()) {
  18758. self.addClass('rtl');
  18759. }
  18760. self.addClass('container');
  18761. self.addClass('container-body', 'body');
  18762. if (settings.containerCls) {
  18763. self.addClass(settings.containerCls);
  18764. }
  18765. self._layout = Factory.create((settings.layout || self.layout) + 'layout');
  18766. if (self.settings.items) {
  18767. self.add(self.settings.items);
  18768. }
  18769. // TODO: Fix this!
  18770. self._hasBody = true;
  18771. },
  18772. /**
  18773. * Returns a collection of child items that the container currently have.
  18774. *
  18775. * @method items
  18776. * @return {tinymce.ui.Collection} Control collection direct child controls.
  18777. */
  18778. items: function() {
  18779. return this._items;
  18780. },
  18781. /**
  18782. * Find child controls by selector.
  18783. *
  18784. * @method find
  18785. * @param {String} selector Selector CSS pattern to find children by.
  18786. * @return {tinymce.ui.Collection} Control collection with child controls.
  18787. */
  18788. find: function(selector) {
  18789. selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
  18790. return selector.find(this);
  18791. },
  18792. /**
  18793. * Adds one or many items to the current container. This will create instances of
  18794. * the object representations if needed.
  18795. *
  18796. * @method add
  18797. * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
  18798. * @return {tinymce.ui.Collection} Current collection control.
  18799. */
  18800. add: function(items) {
  18801. var self = this;
  18802. self.items().add(self.create(items)).parent(self);
  18803. return self;
  18804. },
  18805. /**
  18806. * Focuses the current container instance. This will look
  18807. * for the first control in the container and focus that.
  18808. *
  18809. * @method focus
  18810. * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
  18811. * @return {tinymce.ui.Collection} Current instance.
  18812. */
  18813. focus: function(keyboard) {
  18814. var self = this, focusCtrl, keyboardNav, items;
  18815. if (keyboard) {
  18816. keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
  18817. if (keyboardNav) {
  18818. keyboardNav.focusFirst(self);
  18819. return;
  18820. }
  18821. }
  18822. items = self.find('*');
  18823. // TODO: Figure out a better way to auto focus alert dialog buttons
  18824. if (self.statusbar) {
  18825. items.add(self.statusbar.items());
  18826. }
  18827. items.each(function(ctrl) {
  18828. if (ctrl.settings.autofocus) {
  18829. focusCtrl = null;
  18830. return false;
  18831. }
  18832. if (ctrl.canFocus) {
  18833. focusCtrl = focusCtrl || ctrl;
  18834. }
  18835. });
  18836. if (focusCtrl) {
  18837. focusCtrl.focus();
  18838. }
  18839. return self;
  18840. },
  18841. /**
  18842. * Replaces the specified child control with a new control.
  18843. *
  18844. * @method replace
  18845. * @param {tinymce.ui.Control} oldItem Old item to be replaced.
  18846. * @param {tinymce.ui.Control} newItem New item to be inserted.
  18847. */
  18848. replace: function(oldItem, newItem) {
  18849. var ctrlElm, items = this.items(), i = items.length;
  18850. // Replace the item in collection
  18851. while (i--) {
  18852. if (items[i] === oldItem) {
  18853. items[i] = newItem;
  18854. break;
  18855. }
  18856. }
  18857. if (i >= 0) {
  18858. // Remove new item from DOM
  18859. ctrlElm = newItem.getEl();
  18860. if (ctrlElm) {
  18861. ctrlElm.parentNode.removeChild(ctrlElm);
  18862. }
  18863. // Remove old item from DOM
  18864. ctrlElm = oldItem.getEl();
  18865. if (ctrlElm) {
  18866. ctrlElm.parentNode.removeChild(ctrlElm);
  18867. }
  18868. }
  18869. // Adopt the item
  18870. newItem.parent(this);
  18871. },
  18872. /**
  18873. * Creates the specified items. If any of the items is plain JSON style objects
  18874. * it will convert these into real tinymce.ui.Control instances.
  18875. *
  18876. * @method create
  18877. * @param {Array} items Array of items to convert into control instances.
  18878. * @return {Array} Array with control instances.
  18879. */
  18880. create: function(items) {
  18881. var self = this, settings, ctrlItems = [];
  18882. // Non array structure, then force it into an array
  18883. if (!Tools.isArray(items)) {
  18884. items = [items];
  18885. }
  18886. // Add default type to each child control
  18887. Tools.each(items, function(item) {
  18888. if (item) {
  18889. // Construct item if needed
  18890. if (!(item instanceof Control)) {
  18891. // Name only then convert it to an object
  18892. if (typeof(item) == "string") {
  18893. item = {type: item};
  18894. }
  18895. // Create control instance based on input settings and default settings
  18896. settings = Tools.extend({}, self.settings.defaults, item);
  18897. item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
  18898. (settings.defaults ? settings.defaults.type : null);
  18899. item = Factory.create(settings);
  18900. }
  18901. ctrlItems.push(item);
  18902. }
  18903. });
  18904. return ctrlItems;
  18905. },
  18906. /**
  18907. * Renders new control instances.
  18908. *
  18909. * @private
  18910. */
  18911. renderNew: function() {
  18912. var self = this;
  18913. // Render any new items
  18914. self.items().each(function(ctrl, index) {
  18915. var containerElm, fragment;
  18916. ctrl.parent(self);
  18917. if (!ctrl._rendered) {
  18918. containerElm = self.getEl('body');
  18919. fragment = DomUtils.createFragment(ctrl.renderHtml());
  18920. // Insert or append the item
  18921. if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
  18922. containerElm.insertBefore(fragment, containerElm.childNodes[index]);
  18923. } else {
  18924. containerElm.appendChild(fragment);
  18925. }
  18926. ctrl.postRender();
  18927. }
  18928. });
  18929. self._layout.applyClasses(self);
  18930. self._lastRect = null;
  18931. return self;
  18932. },
  18933. /**
  18934. * Appends new instances to the current container.
  18935. *
  18936. * @method append
  18937. * @param {Array/tinymce.ui.Collection} items Array if controls to append.
  18938. * @return {tinymce.ui.Container} Current container instance.
  18939. */
  18940. append: function(items) {
  18941. return this.add(items).renderNew();
  18942. },
  18943. /**
  18944. * Prepends new instances to the current container.
  18945. *
  18946. * @method prepend
  18947. * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
  18948. * @return {tinymce.ui.Container} Current container instance.
  18949. */
  18950. prepend: function(items) {
  18951. var self = this;
  18952. self.items().set(self.create(items).concat(self.items().toArray()));
  18953. return self.renderNew();
  18954. },
  18955. /**
  18956. * Inserts an control at a specific index.
  18957. *
  18958. * @method insert
  18959. * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
  18960. * @param {Number} index Index to insert controls at.
  18961. * @param {Boolean} [before=false] Inserts controls before the index.
  18962. */
  18963. insert: function(items, index, before) {
  18964. var self = this, curItems, beforeItems, afterItems;
  18965. items = self.create(items);
  18966. curItems = self.items();
  18967. if (!before && index < curItems.length - 1) {
  18968. index += 1;
  18969. }
  18970. if (index >= 0 && index < curItems.length) {
  18971. beforeItems = curItems.slice(0, index).toArray();
  18972. afterItems = curItems.slice(index).toArray();
  18973. curItems.set(beforeItems.concat(items, afterItems));
  18974. }
  18975. return self.renderNew();
  18976. },
  18977. /**
  18978. * Populates the form fields from the specified JSON data object.
  18979. *
  18980. * Control items in the form that matches the data will have it's value set.
  18981. *
  18982. * @method fromJSON
  18983. * @param {Object} data JSON data object to set control values by.
  18984. * @return {tinymce.ui.Container} Current form instance.
  18985. */
  18986. fromJSON: function(data) {
  18987. var self = this;
  18988. for (var name in data) {
  18989. self.find('#' + name).value(data[name]);
  18990. }
  18991. return self;
  18992. },
  18993. /**
  18994. * Serializes the form into a JSON object by getting all items
  18995. * that has a name and a value.
  18996. *
  18997. * @method toJSON
  18998. * @return {Object} JSON object with form data.
  18999. */
  19000. toJSON: function() {
  19001. var self = this, data = {};
  19002. self.find('*').each(function(ctrl) {
  19003. var name = ctrl.name(), value = ctrl.value();
  19004. if (name && typeof(value) != "undefined") {
  19005. data[name] = value;
  19006. }
  19007. });
  19008. return data;
  19009. },
  19010. preRender: function() {
  19011. },
  19012. /**
  19013. * Renders the control as a HTML string.
  19014. *
  19015. * @method renderHtml
  19016. * @return {String} HTML representing the control.
  19017. */
  19018. renderHtml: function() {
  19019. var self = this, layout = self._layout, role = this.settings.role;
  19020. self.preRender();
  19021. layout.preRender(self);
  19022. return (
  19023. '<div id="' + self._id + '" class="' + self.classes() + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
  19024. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  19025. (self.settings.html || '') + layout.renderHtml(self) +
  19026. '</div>' +
  19027. '</div>'
  19028. );
  19029. },
  19030. /**
  19031. * Post render method. Called after the control has been rendered to the target.
  19032. *
  19033. * @method postRender
  19034. * @return {tinymce.ui.Container} Current combobox instance.
  19035. */
  19036. postRender: function() {
  19037. var self = this, box;
  19038. self.items().exec('postRender');
  19039. self._super();
  19040. self._layout.postRender(self);
  19041. self._rendered = true;
  19042. if (self.settings.style) {
  19043. DomUtils.css(self.getEl(), self.settings.style);
  19044. }
  19045. if (self.settings.border) {
  19046. box = self.borderBox();
  19047. DomUtils.css(self.getEl(), {
  19048. 'border-top-width': box.top,
  19049. 'border-right-width': box.right,
  19050. 'border-bottom-width': box.bottom,
  19051. 'border-left-width': box.left
  19052. });
  19053. }
  19054. if (!self.parent()) {
  19055. self.keyboardNav = new KeyboardNavigation({
  19056. root: self
  19057. });
  19058. }
  19059. return self;
  19060. },
  19061. /**
  19062. * Initializes the current controls layout rect.
  19063. * This will be executed by the layout managers to determine the
  19064. * default minWidth/minHeight etc.
  19065. *
  19066. * @method initLayoutRect
  19067. * @return {Object} Layout rect instance.
  19068. */
  19069. initLayoutRect: function() {
  19070. var self = this, layoutRect = self._super();
  19071. // Recalc container size by asking layout manager
  19072. self._layout.recalc(self);
  19073. return layoutRect;
  19074. },
  19075. /**
  19076. * Recalculates the positions of the controls in the current container.
  19077. * This is invoked by the reflow method and shouldn't be called directly.
  19078. *
  19079. * @method recalc
  19080. */
  19081. recalc: function() {
  19082. var self = this, rect = self._layoutRect, lastRect = self._lastRect;
  19083. if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
  19084. self._layout.recalc(self);
  19085. rect = self.layoutRect();
  19086. self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
  19087. return true;
  19088. }
  19089. },
  19090. /**
  19091. * Reflows the current container and it's children and possible parents.
  19092. * This should be used after you for example append children to the current control so
  19093. * that the layout managers know that they need to reposition everything.
  19094. *
  19095. * @example
  19096. * container.append({type: 'button', text: 'My button'}).reflow();
  19097. *
  19098. * @method reflow
  19099. * @return {tinymce.ui.Container} Current container instance.
  19100. */
  19101. reflow: function() {
  19102. var i;
  19103. if (this.visible()) {
  19104. Control.repaintControls = [];
  19105. Control.repaintControls.map = {};
  19106. this.recalc();
  19107. i = Control.repaintControls.length;
  19108. while (i--) {
  19109. Control.repaintControls[i].repaint();
  19110. }
  19111. // TODO: Fix me!
  19112. if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
  19113. this.repaint();
  19114. }
  19115. Control.repaintControls = [];
  19116. }
  19117. return this;
  19118. }
  19119. });
  19120. });
  19121. // Included from: js/tinymce/classes/ui/DragHelper.js
  19122. /**
  19123. * DragHelper.js
  19124. *
  19125. * Copyright, Moxiecode Systems AB
  19126. * Released under LGPL License.
  19127. *
  19128. * License: http://www.tinymce.com/license
  19129. * Contributing: http://www.tinymce.com/contributing
  19130. */
  19131. /**
  19132. * Drag/drop helper class.
  19133. *
  19134. * @example
  19135. * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
  19136. * start: function(evt) {
  19137. * },
  19138. *
  19139. * drag: function(evt) {
  19140. * },
  19141. *
  19142. * end: function(evt) {
  19143. * }
  19144. * });
  19145. *
  19146. * @class tinymce.ui.DragHelper
  19147. */
  19148. define("tinymce/ui/DragHelper", [
  19149. "tinymce/ui/DomUtils"
  19150. ], function(DomUtils) {
  19151. "use strict";
  19152. function getDocumentSize() {
  19153. var doc = document, documentElement, body, scrollWidth, clientWidth;
  19154. var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
  19155. documentElement = doc.documentElement;
  19156. body = doc.body;
  19157. scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
  19158. clientWidth = max(documentElement.clientWidth, body.clientWidth);
  19159. offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
  19160. scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
  19161. clientHeight = max(documentElement.clientHeight, body.clientHeight);
  19162. offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
  19163. return {
  19164. width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
  19165. height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
  19166. };
  19167. }
  19168. return function(id, settings) {
  19169. var eventOverlayElm, doc = document, downButton, start, stop, drag, startX, startY;
  19170. settings = settings || {};
  19171. function getHandleElm() {
  19172. return doc.getElementById(settings.handle || id);
  19173. }
  19174. start = function(e) {
  19175. var docSize = getDocumentSize(), handleElm, cursor;
  19176. e.preventDefault();
  19177. downButton = e.button;
  19178. handleElm = getHandleElm();
  19179. startX = e.screenX;
  19180. startY = e.screenY;
  19181. // Grab cursor from handle
  19182. if (window.getComputedStyle) {
  19183. cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
  19184. } else {
  19185. cursor = handleElm.runtimeStyle.cursor;
  19186. }
  19187. // Create event overlay and add it to document
  19188. eventOverlayElm = doc.createElement('div');
  19189. DomUtils.css(eventOverlayElm, {
  19190. position: "absolute",
  19191. top: 0, left: 0,
  19192. width: docSize.width,
  19193. height: docSize.height,
  19194. zIndex: 0x7FFFFFFF,
  19195. opacity: 0.0001,
  19196. background: 'red',
  19197. cursor: cursor
  19198. });
  19199. doc.body.appendChild(eventOverlayElm);
  19200. // Bind mouse events
  19201. DomUtils.on(doc, 'mousemove', drag);
  19202. DomUtils.on(doc, 'mouseup', stop);
  19203. // Begin drag
  19204. settings.start(e);
  19205. };
  19206. drag = function(e) {
  19207. if (e.button !== downButton) {
  19208. return stop(e);
  19209. }
  19210. e.deltaX = e.screenX - startX;
  19211. e.deltaY = e.screenY - startY;
  19212. e.preventDefault();
  19213. settings.drag(e);
  19214. };
  19215. stop = function(e) {
  19216. DomUtils.off(doc, 'mousemove', drag);
  19217. DomUtils.off(doc, 'mouseup', stop);
  19218. eventOverlayElm.parentNode.removeChild(eventOverlayElm);
  19219. if (settings.stop) {
  19220. settings.stop(e);
  19221. }
  19222. };
  19223. /**
  19224. * Destroys the drag/drop helper instance.
  19225. *
  19226. * @method destroy
  19227. */
  19228. this.destroy = function() {
  19229. DomUtils.off(getHandleElm());
  19230. };
  19231. DomUtils.on(getHandleElm(), 'mousedown', start);
  19232. };
  19233. });
  19234. // Included from: js/tinymce/classes/ui/Scrollable.js
  19235. /**
  19236. * Scrollable.js
  19237. *
  19238. * Copyright, Moxiecode Systems AB
  19239. * Released under LGPL License.
  19240. *
  19241. * License: http://www.tinymce.com/license
  19242. * Contributing: http://www.tinymce.com/contributing
  19243. */
  19244. /**
  19245. * This mixin makes controls scrollable using custom scrollbars.
  19246. *
  19247. * @-x-less Scrollable.less
  19248. * @mixin tinymce.ui.Scrollable
  19249. */
  19250. define("tinymce/ui/Scrollable", [
  19251. "tinymce/ui/DomUtils",
  19252. "tinymce/ui/DragHelper"
  19253. ], function(DomUtils, DragHelper) {
  19254. "use strict";
  19255. return {
  19256. init: function() {
  19257. var self = this;
  19258. self.on('repaint', self.renderScroll);
  19259. },
  19260. renderScroll: function() {
  19261. var self = this, margin = 2;
  19262. function repaintScroll() {
  19263. var hasScrollH, hasScrollV, bodyElm;
  19264. function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
  19265. var containerElm, scrollBarElm, scrollThumbElm;
  19266. var containerSize, scrollSize, ratio, rect;
  19267. var posNameLower, sizeNameLower;
  19268. scrollBarElm = self.getEl('scroll' + axisName);
  19269. if (scrollBarElm) {
  19270. posNameLower = posName.toLowerCase();
  19271. sizeNameLower = sizeName.toLowerCase();
  19272. if (self.getEl('absend')) {
  19273. DomUtils.css(self.getEl('absend'), posNameLower, self.layoutRect()[contentSizeName] - 1);
  19274. }
  19275. if (!hasScroll) {
  19276. DomUtils.css(scrollBarElm, 'display', 'none');
  19277. return;
  19278. }
  19279. DomUtils.css(scrollBarElm, 'display', 'block');
  19280. containerElm = self.getEl('body');
  19281. scrollThumbElm = self.getEl('scroll' + axisName + "t");
  19282. containerSize = containerElm["client" + sizeName] - (margin * 2);
  19283. containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
  19284. scrollSize = containerElm["scroll" + sizeName];
  19285. ratio = containerSize / scrollSize;
  19286. rect = {};
  19287. rect[posNameLower] = containerElm["offset" + posName] + margin;
  19288. rect[sizeNameLower] = containerSize;
  19289. DomUtils.css(scrollBarElm, rect);
  19290. rect = {};
  19291. rect[posNameLower] = containerElm["scroll" + posName] * ratio;
  19292. rect[sizeNameLower] = containerSize * ratio;
  19293. DomUtils.css(scrollThumbElm, rect);
  19294. }
  19295. }
  19296. bodyElm = self.getEl('body');
  19297. hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
  19298. hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
  19299. repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
  19300. repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
  19301. }
  19302. function addScroll() {
  19303. function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
  19304. var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
  19305. self.getEl().appendChild(DomUtils.createFragment(
  19306. '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
  19307. '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
  19308. '</div>'
  19309. ));
  19310. self.draghelper = new DragHelper(axisId + 't', {
  19311. start: function() {
  19312. scrollStart = self.getEl('body')["scroll" + posName];
  19313. DomUtils.addClass(DomUtils.get(axisId), prefix + 'active');
  19314. },
  19315. drag: function(e) {
  19316. var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
  19317. hasScrollH = layoutRect.contentW > layoutRect.innerW;
  19318. hasScrollV = layoutRect.contentH > layoutRect.innerH;
  19319. containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
  19320. containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
  19321. ratio = containerSize / self.getEl('body')["scroll" + sizeName];
  19322. self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
  19323. },
  19324. stop: function() {
  19325. DomUtils.removeClass(DomUtils.get(axisId), prefix + 'active');
  19326. }
  19327. });
  19328. /*
  19329. self.on('click', function(e) {
  19330. if (e.target.id == self._id + '-scrollv') {
  19331. }
  19332. });*/
  19333. }
  19334. self.addClass('scroll');
  19335. addScrollAxis("v", "Top", "Height", "Y", "Width");
  19336. addScrollAxis("h", "Left", "Width", "X", "Height");
  19337. }
  19338. if (self.settings.autoScroll) {
  19339. if (!self._hasScroll) {
  19340. self._hasScroll = true;
  19341. addScroll();
  19342. self.on('wheel', function(e) {
  19343. var bodyEl = self.getEl('body');
  19344. bodyEl.scrollLeft += (e.deltaX || 0) * 10;
  19345. bodyEl.scrollTop += e.deltaY * 10;
  19346. repaintScroll();
  19347. });
  19348. DomUtils.on(self.getEl('body'), "scroll", repaintScroll);
  19349. }
  19350. repaintScroll();
  19351. }
  19352. }
  19353. };
  19354. });
  19355. // Included from: js/tinymce/classes/ui/Panel.js
  19356. /**
  19357. * Panel.js
  19358. *
  19359. * Copyright, Moxiecode Systems AB
  19360. * Released under LGPL License.
  19361. *
  19362. * License: http://www.tinymce.com/license
  19363. * Contributing: http://www.tinymce.com/contributing
  19364. */
  19365. /**
  19366. * Creates a new panel.
  19367. *
  19368. * @-x-less Panel.less
  19369. * @class tinymce.ui.Panel
  19370. * @extends tinymce.ui.Container
  19371. * @mixes tinymce.ui.Scrollable
  19372. */
  19373. define("tinymce/ui/Panel", [
  19374. "tinymce/ui/Container",
  19375. "tinymce/ui/Scrollable"
  19376. ], function(Container, Scrollable) {
  19377. "use strict";
  19378. return Container.extend({
  19379. Defaults: {
  19380. layout: 'fit',
  19381. containerCls: 'panel'
  19382. },
  19383. Mixins: [Scrollable],
  19384. /**
  19385. * Renders the control as a HTML string.
  19386. *
  19387. * @method renderHtml
  19388. * @return {String} HTML representing the control.
  19389. */
  19390. renderHtml: function() {
  19391. var self = this, layout = self._layout, innerHtml = self.settings.html;
  19392. self.preRender();
  19393. layout.preRender(self);
  19394. if (typeof(innerHtml) == "undefined") {
  19395. innerHtml = (
  19396. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  19397. layout.renderHtml(self) +
  19398. '</div>'
  19399. );
  19400. } else {
  19401. if (typeof(innerHtml) == 'function') {
  19402. innerHtml = innerHtml.call(self);
  19403. }
  19404. self._hasBody = false;
  19405. }
  19406. return (
  19407. '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1" role="group">' +
  19408. (self._preBodyHtml || '') +
  19409. innerHtml +
  19410. '</div>'
  19411. );
  19412. }
  19413. });
  19414. });
  19415. // Included from: js/tinymce/classes/ui/Movable.js
  19416. /**
  19417. * Movable.js
  19418. *
  19419. * Copyright, Moxiecode Systems AB
  19420. * Released under LGPL License.
  19421. *
  19422. * License: http://www.tinymce.com/license
  19423. * Contributing: http://www.tinymce.com/contributing
  19424. */
  19425. /**
  19426. * Movable mixin. Makes controls movable absolute and relative to other elements.
  19427. *
  19428. * @mixin tinymce.ui.Movable
  19429. */
  19430. define("tinymce/ui/Movable", [
  19431. "tinymce/ui/DomUtils"
  19432. ], function(DomUtils) {
  19433. "use strict";
  19434. function calculateRelativePosition(ctrl, targetElm, rel) {
  19435. var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
  19436. viewport = DomUtils.getViewPort();
  19437. // Get pos of target
  19438. pos = DomUtils.getPos(targetElm);
  19439. x = pos.x;
  19440. y = pos.y;
  19441. if (ctrl._fixed) {
  19442. x -= viewport.x;
  19443. y -= viewport.y;
  19444. }
  19445. // Get size of self
  19446. ctrlElm = ctrl.getEl();
  19447. size = DomUtils.getSize(ctrlElm);
  19448. selfW = size.width;
  19449. selfH = size.height;
  19450. // Get size of target
  19451. size = DomUtils.getSize(targetElm);
  19452. targetW = size.width;
  19453. targetH = size.height;
  19454. // Parse align string
  19455. rel = (rel || '').split('');
  19456. // Target corners
  19457. if (rel[0] === 'b') {
  19458. y += targetH;
  19459. }
  19460. if (rel[1] === 'r') {
  19461. x += targetW;
  19462. }
  19463. if (rel[0] === 'c') {
  19464. y += Math.round(targetH / 2);
  19465. }
  19466. if (rel[1] === 'c') {
  19467. x += Math.round(targetW / 2);
  19468. }
  19469. // Self corners
  19470. if (rel[3] === 'b') {
  19471. y -= selfH;
  19472. }
  19473. if (rel[4] === 'r') {
  19474. x -= selfW;
  19475. }
  19476. if (rel[3] === 'c') {
  19477. y -= Math.round(selfH / 2);
  19478. }
  19479. if (rel[4] === 'c') {
  19480. x -= Math.round(selfW / 2);
  19481. }
  19482. return {
  19483. x: x,
  19484. y: y,
  19485. w: selfW,
  19486. h: selfH
  19487. };
  19488. }
  19489. return {
  19490. /**
  19491. * Tests various positions to get the most suitable one.
  19492. *
  19493. * @method testMoveRel
  19494. * @param {DOMElement} elm Element to position against.
  19495. * @param {Array} rels Array with relative positions.
  19496. * @return {String} Best suitable relative position.
  19497. */
  19498. testMoveRel: function(elm, rels) {
  19499. var viewPortRect = DomUtils.getViewPort();
  19500. for (var i = 0; i < rels.length; i++) {
  19501. var pos = calculateRelativePosition(this, elm, rels[i]);
  19502. if (this._fixed) {
  19503. if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
  19504. return rels[i];
  19505. }
  19506. } else {
  19507. if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
  19508. pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
  19509. return rels[i];
  19510. }
  19511. }
  19512. }
  19513. return rels[0];
  19514. },
  19515. /**
  19516. * Move relative to the specified element.
  19517. *
  19518. * @method moveRel
  19519. * @param {Element} elm Element to move relative to.
  19520. * @param {String} rel Relative mode. For example: br-tl.
  19521. * @return {tinymce.ui.Control} Current control instance.
  19522. */
  19523. moveRel: function(elm, rel) {
  19524. if (typeof(rel) != 'string') {
  19525. rel = this.testMoveRel(elm, rel);
  19526. }
  19527. var pos = calculateRelativePosition(this, elm, rel);
  19528. return this.moveTo(pos.x, pos.y);
  19529. },
  19530. /**
  19531. * Move by a relative x, y values.
  19532. *
  19533. * @method moveBy
  19534. * @param {Number} dx Relative x position.
  19535. * @param {Number} dy Relative y position.
  19536. * @return {tinymce.ui.Control} Current control instance.
  19537. */
  19538. moveBy: function(dx, dy) {
  19539. var self = this, rect = self.layoutRect();
  19540. self.moveTo(rect.x + dx, rect.y + dy);
  19541. return self;
  19542. },
  19543. /**
  19544. * Move to absolute position.
  19545. *
  19546. * @method moveTo
  19547. * @param {Number} x Absolute x position.
  19548. * @param {Number} y Absolute y position.
  19549. * @return {tinymce.ui.Control} Current control instance.
  19550. */
  19551. moveTo: function(x, y) {
  19552. var self = this;
  19553. // TODO: Move this to some global class
  19554. function contrain(value, max, size) {
  19555. if (value < 0) {
  19556. return 0;
  19557. }
  19558. if (value + size > max) {
  19559. value = max - size;
  19560. return value < 0 ? 0 : value;
  19561. }
  19562. return value;
  19563. }
  19564. if (self.settings.constrainToViewport) {
  19565. var viewPortRect = DomUtils.getViewPort(window);
  19566. var layoutRect = self.layoutRect();
  19567. x = contrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
  19568. y = contrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
  19569. }
  19570. if (self._rendered) {
  19571. self.layoutRect({x: x, y: y}).repaint();
  19572. } else {
  19573. self.settings.x = x;
  19574. self.settings.y = y;
  19575. }
  19576. self.fire('move', {x: x, y: y});
  19577. return self;
  19578. }
  19579. };
  19580. });
  19581. // Included from: js/tinymce/classes/ui/Resizable.js
  19582. /**
  19583. * Resizable.js
  19584. *
  19585. * Copyright, Moxiecode Systems AB
  19586. * Released under LGPL License.
  19587. *
  19588. * License: http://www.tinymce.com/license
  19589. * Contributing: http://www.tinymce.com/contributing
  19590. */
  19591. /**
  19592. * Resizable mixin. Enables controls to be resized.
  19593. *
  19594. * @mixin tinymce.ui.Resizable
  19595. */
  19596. define("tinymce/ui/Resizable", [
  19597. "tinymce/ui/DomUtils"
  19598. ], function(DomUtils) {
  19599. "use strict";
  19600. return {
  19601. /**
  19602. * Resizes the control to contents.
  19603. *
  19604. * @method resizeToContent
  19605. */
  19606. resizeToContent: function() {
  19607. this._layoutRect.autoResize = true;
  19608. this._lastRect = null;
  19609. this.reflow();
  19610. },
  19611. /**
  19612. * Resizes the control to a specific width/height.
  19613. *
  19614. * @method resizeTo
  19615. * @param {Number} w Control width.
  19616. * @param {Number} h Control height.
  19617. * @return {tinymce.ui.Control} Current control instance.
  19618. */
  19619. resizeTo: function(w, h) {
  19620. // TODO: Fix hack
  19621. if (w <= 1 || h <= 1) {
  19622. var rect = DomUtils.getWindowSize();
  19623. w = w <= 1 ? w * rect.w : w;
  19624. h = h <= 1 ? h * rect.h : h;
  19625. }
  19626. this._layoutRect.autoResize = false;
  19627. return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
  19628. },
  19629. /**
  19630. * Resizes the control to a specific relative width/height.
  19631. *
  19632. * @method resizeBy
  19633. * @param {Number} dw Relative control width.
  19634. * @param {Number} dh Relative control height.
  19635. * @return {tinymce.ui.Control} Current control instance.
  19636. */
  19637. resizeBy: function(dw, dh) {
  19638. var self = this, rect = self.layoutRect();
  19639. return self.resizeTo(rect.w + dw, rect.h + dh);
  19640. }
  19641. };
  19642. });
  19643. // Included from: js/tinymce/classes/ui/FloatPanel.js
  19644. /**
  19645. * FloatPanel.js
  19646. *
  19647. * Copyright, Moxiecode Systems AB
  19648. * Released under LGPL License.
  19649. *
  19650. * License: http://www.tinymce.com/license
  19651. * Contributing: http://www.tinymce.com/contributing
  19652. */
  19653. /**
  19654. * This class creates a floating panel.
  19655. *
  19656. * @-x-less FloatPanel.less
  19657. * @class tinymce.ui.FloatPanel
  19658. * @extends tinymce.ui.Panel
  19659. * @mixes tinymce.ui.Movable
  19660. * @mixes tinymce.ui.Resizable
  19661. */
  19662. define("tinymce/ui/FloatPanel", [
  19663. "tinymce/ui/Panel",
  19664. "tinymce/ui/Movable",
  19665. "tinymce/ui/Resizable",
  19666. "tinymce/ui/DomUtils"
  19667. ], function(Panel, Movable, Resizable, DomUtils) {
  19668. "use strict";
  19669. var documentClickHandler, documentScrollHandler, visiblePanels = [];
  19670. var zOrder = [], hasModal;
  19671. var FloatPanel = Panel.extend({
  19672. Mixins: [Movable, Resizable],
  19673. /**
  19674. * Constructs a new control instance with the specified settings.
  19675. *
  19676. * @constructor
  19677. * @param {Object} settings Name/value object with settings.
  19678. * @setting {Boolean} autohide Automatically hide the panel.
  19679. */
  19680. init: function(settings) {
  19681. var self = this;
  19682. function reorder() {
  19683. var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
  19684. if (zOrder.length) {
  19685. for (i = 0; i < zOrder.length; i++) {
  19686. if (zOrder[i].modal) {
  19687. zIndex++;
  19688. topModal = zOrder[i];
  19689. }
  19690. zOrder[i].getEl().style.zIndex = zIndex;
  19691. zOrder[i].zIndex = zIndex;
  19692. zIndex++;
  19693. }
  19694. }
  19695. var modalBlockEl = document.getElementById(self.classPrefix + 'modal-block');
  19696. if (topModal) {
  19697. DomUtils.css(modalBlockEl, 'z-index', topModal.zIndex - 1);
  19698. } else if (modalBlockEl) {
  19699. modalBlockEl.parentNode.removeChild(modalBlockEl);
  19700. hasModal = false;
  19701. }
  19702. FloatPanel.currentZIndex = zIndex;
  19703. }
  19704. function isChildOf(ctrl, parent) {
  19705. while (ctrl) {
  19706. if (ctrl == parent) {
  19707. return true;
  19708. }
  19709. ctrl = ctrl.parent();
  19710. }
  19711. }
  19712. /**
  19713. * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
  19714. * also reposition all child panels of the current panel.
  19715. */
  19716. function repositionPanel(panel) {
  19717. var scrollY = DomUtils.getViewPort().y;
  19718. function toggleFixedChildPanels(fixed, deltaY) {
  19719. var parent;
  19720. for (var i = 0; i < visiblePanels.length; i++) {
  19721. if (visiblePanels[i] != panel) {
  19722. parent = visiblePanels[i].parent();
  19723. while (parent && (parent = parent.parent())) {
  19724. if (parent == panel) {
  19725. visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
  19726. }
  19727. }
  19728. }
  19729. }
  19730. }
  19731. if (panel.settings.autofix) {
  19732. if (!panel._fixed) {
  19733. panel._autoFixY = panel.layoutRect().y;
  19734. if (panel._autoFixY < scrollY) {
  19735. panel.fixed(true).layoutRect({y: 0}).repaint();
  19736. toggleFixedChildPanels(true, scrollY - panel._autoFixY);
  19737. }
  19738. } else {
  19739. if (panel._autoFixY > scrollY) {
  19740. panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
  19741. toggleFixedChildPanels(false, panel._autoFixY - scrollY);
  19742. }
  19743. }
  19744. }
  19745. }
  19746. self._super(settings);
  19747. self._eventsRoot = self;
  19748. self.addClass('floatpanel');
  19749. // Hide floatpanes on click out side the root button
  19750. if (settings.autohide) {
  19751. if (!documentClickHandler) {
  19752. documentClickHandler = function(e) {
  19753. // Hide any float panel when a click is out side that float panel and the
  19754. // float panels direct parent for example a click on a menu button
  19755. var i = visiblePanels.length;
  19756. while (i--) {
  19757. var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
  19758. if (panel.settings.autohide) {
  19759. if (clickCtrl) {
  19760. if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
  19761. continue;
  19762. }
  19763. }
  19764. e = panel.fire('autohide', {target: e.target});
  19765. if (!e.isDefaultPrevented()) {
  19766. panel.hide();
  19767. }
  19768. }
  19769. }
  19770. };
  19771. DomUtils.on(document, 'click', documentClickHandler);
  19772. }
  19773. visiblePanels.push(self);
  19774. }
  19775. if (settings.autofix) {
  19776. if (!documentScrollHandler) {
  19777. documentScrollHandler = function() {
  19778. var i;
  19779. i = visiblePanels.length;
  19780. while (i--) {
  19781. repositionPanel(visiblePanels[i]);
  19782. }
  19783. };
  19784. DomUtils.on(window, 'scroll', documentScrollHandler);
  19785. }
  19786. self.on('move', function() {
  19787. repositionPanel(this);
  19788. });
  19789. }
  19790. self.on('postrender show', function(e) {
  19791. if (e.control == self) {
  19792. var modalBlockEl, prefix = self.classPrefix;
  19793. if (self.modal && !hasModal) {
  19794. modalBlockEl = DomUtils.createFragment('<div id="' + prefix + 'modal-block" class="' +
  19795. prefix + 'reset ' + prefix + 'fade"></div>');
  19796. modalBlockEl = modalBlockEl.firstChild;
  19797. self.getContainerElm().appendChild(modalBlockEl);
  19798. setTimeout(function() {
  19799. DomUtils.addClass(modalBlockEl, prefix + 'in');
  19800. DomUtils.addClass(self.getEl(), prefix + 'in');
  19801. }, 0);
  19802. hasModal = true;
  19803. }
  19804. zOrder.push(self);
  19805. reorder();
  19806. }
  19807. });
  19808. self.on('close hide', function(e) {
  19809. if (e.control == self) {
  19810. var i = zOrder.length;
  19811. while (i--) {
  19812. if (zOrder[i] === self) {
  19813. zOrder.splice(i, 1);
  19814. }
  19815. }
  19816. reorder();
  19817. }
  19818. });
  19819. self.on('show', function() {
  19820. self.parents().each(function(ctrl) {
  19821. if (ctrl._fixed) {
  19822. self.fixed(true);
  19823. return false;
  19824. }
  19825. });
  19826. });
  19827. if (settings.popover) {
  19828. self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
  19829. self.addClass('popover').addClass('bottom').addClass(self.isRtl() ? 'end' : 'start');
  19830. }
  19831. },
  19832. fixed: function(state) {
  19833. var self = this;
  19834. if (self._fixed != state) {
  19835. if (self._rendered) {
  19836. var viewport = DomUtils.getViewPort();
  19837. if (state) {
  19838. self.layoutRect().y -= viewport.y;
  19839. } else {
  19840. self.layoutRect().y += viewport.y;
  19841. }
  19842. }
  19843. self.toggleClass('fixed', state);
  19844. self._fixed = state;
  19845. }
  19846. return self;
  19847. },
  19848. /**
  19849. * Shows the current float panel.
  19850. *
  19851. * @method show
  19852. * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
  19853. */
  19854. show: function() {
  19855. var self = this, i, state = self._super();
  19856. i = visiblePanels.length;
  19857. while (i--) {
  19858. if (visiblePanels[i] === self) {
  19859. break;
  19860. }
  19861. }
  19862. if (i === -1) {
  19863. visiblePanels.push(self);
  19864. }
  19865. return state;
  19866. },
  19867. /**
  19868. * Hides the current float panel.
  19869. *
  19870. * @method hide
  19871. * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
  19872. */
  19873. hide: function() {
  19874. removeVisiblePanel(this);
  19875. return this._super();
  19876. },
  19877. /**
  19878. * Hides all visible the float panels.
  19879. *
  19880. * @method hideAll
  19881. */
  19882. hideAll: function() {
  19883. FloatPanel.hideAll();
  19884. },
  19885. /**
  19886. * Closes the float panel. This will remove the float panel from page and fire the close event.
  19887. *
  19888. * @method close
  19889. */
  19890. close: function() {
  19891. var self = this;
  19892. self.fire('close');
  19893. return self.remove();
  19894. },
  19895. /**
  19896. * Removes the float panel from page.
  19897. *
  19898. * @method remove
  19899. */
  19900. remove: function() {
  19901. removeVisiblePanel(this);
  19902. this._super();
  19903. },
  19904. postRender: function() {
  19905. var self = this;
  19906. if (self.settings.bodyRole) {
  19907. this.getEl('body').setAttribute('role', self.settings.bodyRole);
  19908. }
  19909. return self._super();
  19910. }
  19911. });
  19912. /**
  19913. * Hides all visible the float panels.
  19914. *
  19915. * @static
  19916. * @method hideAll
  19917. */
  19918. FloatPanel.hideAll = function() {
  19919. var i = visiblePanels.length;
  19920. while (i--) {
  19921. var panel = visiblePanels[i];
  19922. if (panel && panel.settings.autohide) {
  19923. panel.hide();
  19924. visiblePanels.splice(i, 1);
  19925. }
  19926. }
  19927. };
  19928. function removeVisiblePanel(panel) {
  19929. var i;
  19930. i = visiblePanels.length;
  19931. while (i--) {
  19932. if (visiblePanels[i] === panel) {
  19933. visiblePanels.splice(i, 1);
  19934. }
  19935. }
  19936. i = zOrder.length;
  19937. while (i--) {
  19938. if (zOrder[i] === panel) {
  19939. zOrder.splice(i, 1);
  19940. }
  19941. }
  19942. }
  19943. return FloatPanel;
  19944. });
  19945. // Included from: js/tinymce/classes/ui/Window.js
  19946. /**
  19947. * Window.js
  19948. *
  19949. * Copyright, Moxiecode Systems AB
  19950. * Released under LGPL License.
  19951. *
  19952. * License: http://www.tinymce.com/license
  19953. * Contributing: http://www.tinymce.com/contributing
  19954. */
  19955. /**
  19956. * Creates a new window.
  19957. *
  19958. * @-x-less Window.less
  19959. * @class tinymce.ui.Window
  19960. * @extends tinymce.ui.FloatPanel
  19961. */
  19962. define("tinymce/ui/Window", [
  19963. "tinymce/ui/FloatPanel",
  19964. "tinymce/ui/Panel",
  19965. "tinymce/ui/DomUtils",
  19966. "tinymce/ui/DragHelper"
  19967. ], function(FloatPanel, Panel, DomUtils, DragHelper) {
  19968. "use strict";
  19969. var Window = FloatPanel.extend({
  19970. modal: true,
  19971. Defaults: {
  19972. border: 1,
  19973. layout: 'flex',
  19974. containerCls: 'panel',
  19975. role: 'dialog',
  19976. callbacks: {
  19977. submit: function() {
  19978. this.fire('submit', {data: this.toJSON()});
  19979. },
  19980. close: function() {
  19981. this.close();
  19982. }
  19983. }
  19984. },
  19985. /**
  19986. * Constructs a instance with the specified settings.
  19987. *
  19988. * @constructor
  19989. * @param {Object} settings Name/value object with settings.
  19990. */
  19991. init: function(settings) {
  19992. var self = this;
  19993. self._super(settings);
  19994. if (self.isRtl()) {
  19995. self.addClass('rtl');
  19996. }
  19997. self.addClass('window');
  19998. self._fixed = true;
  19999. // Create statusbar
  20000. if (settings.buttons) {
  20001. self.statusbar = new Panel({
  20002. layout: 'flex',
  20003. border: '1 0 0 0',
  20004. spacing: 3,
  20005. padding: 10,
  20006. align: 'center',
  20007. pack: self.isRtl() ? 'start' : 'end',
  20008. defaults: {
  20009. type: 'button'
  20010. },
  20011. items: settings.buttons
  20012. });
  20013. self.statusbar.addClass('foot');
  20014. self.statusbar.parent(self);
  20015. }
  20016. self.on('click', function(e) {
  20017. if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
  20018. self.close();
  20019. }
  20020. });
  20021. self.on('cancel', function() {
  20022. self.close();
  20023. });
  20024. self.aria('describedby', self.describedBy || self._id + '-none');
  20025. self.aria('label', settings.title);
  20026. self._fullscreen = false;
  20027. },
  20028. /**
  20029. * Recalculates the positions of the controls in the current container.
  20030. * This is invoked by the reflow method and shouldn't be called directly.
  20031. *
  20032. * @method recalc
  20033. */
  20034. recalc: function() {
  20035. var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
  20036. if (self._fullscreen) {
  20037. self.layoutRect(DomUtils.getWindowSize());
  20038. self.layoutRect().contentH = self.layoutRect().innerH;
  20039. }
  20040. self._super();
  20041. layoutRect = self.layoutRect();
  20042. // Resize window based on title width
  20043. if (self.settings.title && !self._fullscreen) {
  20044. width = layoutRect.headerW;
  20045. if (width > layoutRect.w) {
  20046. x = layoutRect.x - Math.max(0, width / 2);
  20047. self.layoutRect({w: width, x: x});
  20048. needsRecalc = true;
  20049. }
  20050. }
  20051. // Resize window based on statusbar width
  20052. if (statusbar) {
  20053. statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
  20054. width = statusbar.layoutRect().minW + layoutRect.deltaW;
  20055. if (width > layoutRect.w) {
  20056. x = layoutRect.x - Math.max(0, width - layoutRect.w);
  20057. self.layoutRect({w: width, x: x});
  20058. needsRecalc = true;
  20059. }
  20060. }
  20061. // Recalc body and disable auto resize
  20062. if (needsRecalc) {
  20063. self.recalc();
  20064. }
  20065. },
  20066. /**
  20067. * Initializes the current controls layout rect.
  20068. * This will be executed by the layout managers to determine the
  20069. * default minWidth/minHeight etc.
  20070. *
  20071. * @method initLayoutRect
  20072. * @return {Object} Layout rect instance.
  20073. */
  20074. initLayoutRect: function() {
  20075. var self = this, layoutRect = self._super(), deltaH = 0, headEl;
  20076. // Reserve vertical space for title
  20077. if (self.settings.title && !self._fullscreen) {
  20078. headEl = self.getEl('head');
  20079. var size = DomUtils.getSize(headEl);
  20080. layoutRect.headerW = size.width;
  20081. layoutRect.headerH = size.height;
  20082. deltaH += layoutRect.headerH;
  20083. }
  20084. // Reserve vertical space for statusbar
  20085. if (self.statusbar) {
  20086. deltaH += self.statusbar.layoutRect().h;
  20087. }
  20088. layoutRect.deltaH += deltaH;
  20089. layoutRect.minH += deltaH;
  20090. //layoutRect.innerH -= deltaH;
  20091. layoutRect.h += deltaH;
  20092. var rect = DomUtils.getWindowSize();
  20093. layoutRect.x = Math.max(0, rect.w / 2 - layoutRect.w / 2);
  20094. layoutRect.y = Math.max(0, rect.h / 2 - layoutRect.h / 2);
  20095. return layoutRect;
  20096. },
  20097. /**
  20098. * Renders the control as a HTML string.
  20099. *
  20100. * @method renderHtml
  20101. * @return {String} HTML representing the control.
  20102. */
  20103. renderHtml: function() {
  20104. var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
  20105. var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
  20106. self.preRender();
  20107. layout.preRender(self);
  20108. if (settings.title) {
  20109. headerHtml = (
  20110. '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
  20111. '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
  20112. '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>' +
  20113. '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
  20114. '</div>'
  20115. );
  20116. }
  20117. if (settings.url) {
  20118. html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
  20119. }
  20120. if (typeof(html) == "undefined") {
  20121. html = layout.renderHtml(self);
  20122. }
  20123. if (self.statusbar) {
  20124. footerHtml = self.statusbar.renderHtml();
  20125. }
  20126. return (
  20127. '<div id="' + id + '" class="' + self.classes() + '" hidefocus="1">' +
  20128. '<div class="' + self.classPrefix + 'reset" role="application">' +
  20129. headerHtml +
  20130. '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
  20131. html +
  20132. '</div>' +
  20133. footerHtml +
  20134. '</div>' +
  20135. '</div>'
  20136. );
  20137. },
  20138. /**
  20139. * Switches the window fullscreen mode.
  20140. *
  20141. * @method fullscreen
  20142. * @param {Boolean} state True/false state.
  20143. * @return {tinymce.ui.Window} Current window instance.
  20144. */
  20145. fullscreen: function(state) {
  20146. var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
  20147. if (state != self._fullscreen) {
  20148. DomUtils.on(window, 'resize', function() {
  20149. var time;
  20150. if (self._fullscreen) {
  20151. // Time the layout time if it's to slow use a timeout to not hog the CPU
  20152. if (!slowRendering) {
  20153. time = new Date().getTime();
  20154. var rect = DomUtils.getWindowSize();
  20155. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  20156. if ((new Date().getTime()) - time > 50) {
  20157. slowRendering = true;
  20158. }
  20159. } else {
  20160. if (!self._timer) {
  20161. self._timer = setTimeout(function() {
  20162. var rect = DomUtils.getWindowSize();
  20163. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  20164. self._timer = 0;
  20165. }, 50);
  20166. }
  20167. }
  20168. }
  20169. });
  20170. layoutRect = self.layoutRect();
  20171. self._fullscreen = state;
  20172. if (!state) {
  20173. self._borderBox = self.parseBox(self.settings.border);
  20174. self.getEl('head').style.display = '';
  20175. layoutRect.deltaH += layoutRect.headerH;
  20176. DomUtils.removeClass(documentElement, prefix + 'fullscreen');
  20177. DomUtils.removeClass(document.body, prefix + 'fullscreen');
  20178. self.removeClass('fullscreen');
  20179. self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
  20180. } else {
  20181. self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
  20182. self._borderBox = self.parseBox('0');
  20183. self.getEl('head').style.display = 'none';
  20184. layoutRect.deltaH -= layoutRect.headerH + 2;
  20185. DomUtils.addClass(documentElement, prefix + 'fullscreen');
  20186. DomUtils.addClass(document.body, prefix + 'fullscreen');
  20187. self.addClass('fullscreen');
  20188. var rect = DomUtils.getWindowSize();
  20189. self.moveTo(0, 0).resizeTo(rect.w, rect.h);
  20190. }
  20191. }
  20192. return self.reflow();
  20193. },
  20194. /**
  20195. * Called after the control has been rendered.
  20196. *
  20197. * @method postRender
  20198. */
  20199. postRender: function() {
  20200. var self = this, startPos;
  20201. setTimeout(function() {
  20202. self.addClass('in');
  20203. }, 0);
  20204. self._super();
  20205. if (self.statusbar) {
  20206. self.statusbar.postRender();
  20207. }
  20208. self.focus();
  20209. this.dragHelper = new DragHelper(self._id + '-dragh', {
  20210. start: function() {
  20211. startPos = {
  20212. x: self.layoutRect().x,
  20213. y: self.layoutRect().y
  20214. };
  20215. },
  20216. drag: function(e) {
  20217. self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
  20218. }
  20219. });
  20220. self.on('submit', function(e) {
  20221. if (!e.isDefaultPrevented()) {
  20222. self.close();
  20223. }
  20224. });
  20225. },
  20226. /**
  20227. * Fires a submit event with the serialized form.
  20228. *
  20229. * @method submit
  20230. * @return {Object} Event arguments object.
  20231. */
  20232. submit: function() {
  20233. return this.fire('submit', {data: this.toJSON()});
  20234. },
  20235. /**
  20236. * Removes the current control from DOM and from UI collections.
  20237. *
  20238. * @method remove
  20239. * @return {tinymce.ui.Control} Current control instance.
  20240. */
  20241. remove: function() {
  20242. var self = this, prefix = self.classPrefix;
  20243. self.dragHelper.destroy();
  20244. self._super();
  20245. if (self.statusbar) {
  20246. this.statusbar.remove();
  20247. }
  20248. if (self._fullscreen) {
  20249. DomUtils.removeClass(document.documentElement, prefix + 'fullscreen');
  20250. DomUtils.removeClass(document.body, prefix + 'fullscreen');
  20251. }
  20252. }
  20253. });
  20254. return Window;
  20255. });
  20256. // Included from: js/tinymce/classes/ui/MessageBox.js
  20257. /**
  20258. * MessageBox.js
  20259. *
  20260. * Copyright, Moxiecode Systems AB
  20261. * Released under LGPL License.
  20262. *
  20263. * License: http://www.tinymce.com/license
  20264. * Contributing: http://www.tinymce.com/contributing
  20265. */
  20266. /**
  20267. * This class is used to create MessageBoxes like alerts/confirms etc.
  20268. *
  20269. * @class tinymce.ui.Window
  20270. * @extends tinymce.ui.FloatPanel
  20271. */
  20272. define("tinymce/ui/MessageBox", [
  20273. "tinymce/ui/Window"
  20274. ], function(Window) {
  20275. "use strict";
  20276. var MessageBox = Window.extend({
  20277. /**
  20278. * Constructs a instance with the specified settings.
  20279. *
  20280. * @constructor
  20281. * @param {Object} settings Name/value object with settings.
  20282. */
  20283. init: function(settings) {
  20284. settings = {
  20285. border: 1,
  20286. padding: 20,
  20287. layout: 'flex',
  20288. pack: "center",
  20289. align: "center",
  20290. containerCls: 'panel',
  20291. autoScroll: true,
  20292. buttons: {type: "button", text: "Ok", action: "ok"},
  20293. items: {
  20294. type: "label",
  20295. multiline: true,
  20296. maxWidth: 500,
  20297. maxHeight: 200
  20298. }
  20299. };
  20300. this._super(settings);
  20301. },
  20302. Statics: {
  20303. /**
  20304. * Ok buttons constant.
  20305. *
  20306. * @static
  20307. * @final
  20308. * @field {Number} OK
  20309. */
  20310. OK: 1,
  20311. /**
  20312. * Ok/cancel buttons constant.
  20313. *
  20314. * @static
  20315. * @final
  20316. * @field {Number} OK_CANCEL
  20317. */
  20318. OK_CANCEL: 2,
  20319. /**
  20320. * yes/no buttons constant.
  20321. *
  20322. * @static
  20323. * @final
  20324. * @field {Number} YES_NO
  20325. */
  20326. YES_NO: 3,
  20327. /**
  20328. * yes/no/cancel buttons constant.
  20329. *
  20330. * @static
  20331. * @final
  20332. * @field {Number} YES_NO_CANCEL
  20333. */
  20334. YES_NO_CANCEL: 4,
  20335. /**
  20336. * Constructs a new message box and renders it to the body element.
  20337. *
  20338. * @static
  20339. * @method msgBox
  20340. * @param {Object} settings Name/value object with settings.
  20341. */
  20342. msgBox: function(settings) {
  20343. var buttons, callback = settings.callback || function() {};
  20344. switch (settings.buttons) {
  20345. case MessageBox.OK_CANCEL:
  20346. buttons = [
  20347. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20348. e.control.parents()[1].close();
  20349. callback(true);
  20350. }},
  20351. {type: "button", text: "Cancel", onClick: function(e) {
  20352. e.control.parents()[1].close();
  20353. callback(false);
  20354. }}
  20355. ];
  20356. break;
  20357. case MessageBox.YES_NO:
  20358. buttons = [
  20359. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20360. e.control.parents()[1].close();
  20361. callback(true);
  20362. }}
  20363. ];
  20364. break;
  20365. case MessageBox.YES_NO_CANCEL:
  20366. buttons = [
  20367. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20368. e.control.parents()[1].close();
  20369. }}
  20370. ];
  20371. break;
  20372. default:
  20373. buttons = [
  20374. {type: "button", text: "Ok", subtype: "primary", onClick: function(e) {
  20375. e.control.parents()[1].close();
  20376. callback(true);
  20377. }}
  20378. ];
  20379. break;
  20380. }
  20381. return new Window({
  20382. padding: 20,
  20383. x: settings.x,
  20384. y: settings.y,
  20385. minWidth: 300,
  20386. minHeight: 100,
  20387. layout: "flex",
  20388. pack: "center",
  20389. align: "center",
  20390. buttons: buttons,
  20391. title: settings.title,
  20392. role: 'alertdialog',
  20393. items: {
  20394. type: "label",
  20395. multiline: true,
  20396. maxWidth: 500,
  20397. maxHeight: 200,
  20398. text: settings.text
  20399. },
  20400. onPostRender: function() {
  20401. this.aria('describedby', this.items()[0]._id);
  20402. },
  20403. onClose: settings.onClose,
  20404. onCancel: function() {
  20405. callback(false);
  20406. }
  20407. }).renderTo(document.body).reflow();
  20408. },
  20409. /**
  20410. * Creates a new alert dialog.
  20411. *
  20412. * @method alert
  20413. * @param {Object} settings Settings for the alert dialog.
  20414. * @param {function} [callback] Callback to execute when the user makes a choice.
  20415. */
  20416. alert: function(settings, callback) {
  20417. if (typeof(settings) == "string") {
  20418. settings = {text: settings};
  20419. }
  20420. settings.callback = callback;
  20421. return MessageBox.msgBox(settings);
  20422. },
  20423. /**
  20424. * Creates a new confirm dialog.
  20425. *
  20426. * @method confirm
  20427. * @param {Object} settings Settings for the confirm dialog.
  20428. * @param {function} [callback] Callback to execute when the user makes a choice.
  20429. */
  20430. confirm: function(settings, callback) {
  20431. if (typeof(settings) == "string") {
  20432. settings = {text: settings};
  20433. }
  20434. settings.callback = callback;
  20435. settings.buttons = MessageBox.OK_CANCEL;
  20436. return MessageBox.msgBox(settings);
  20437. }
  20438. }
  20439. });
  20440. return MessageBox;
  20441. });
  20442. // Included from: js/tinymce/classes/WindowManager.js
  20443. /**
  20444. * WindowManager.js
  20445. *
  20446. * Copyright, Moxiecode Systems AB
  20447. * Released under LGPL License.
  20448. *
  20449. * License: http://www.tinymce.com/license
  20450. * Contributing: http://www.tinymce.com/contributing
  20451. */
  20452. /**
  20453. * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
  20454. *
  20455. * @class tinymce.WindowManager
  20456. * @example
  20457. * // Opens a new dialog with the file.htm file and the size 320x240
  20458. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  20459. * tinymce.activeEditor.windowManager.open({
  20460. * url: 'file.htm',
  20461. * width: 320,
  20462. * height: 240
  20463. * }, {
  20464. * custom_param: 1
  20465. * });
  20466. *
  20467. * // Displays an alert box using the active editors window manager instance
  20468. * tinymce.activeEditor.windowManager.alert('Hello world!');
  20469. *
  20470. * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  20471. * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  20472. * if (s)
  20473. * tinymce.activeEditor.windowManager.alert("Ok");
  20474. * else
  20475. * tinymce.activeEditor.windowManager.alert("Cancel");
  20476. * });
  20477. */
  20478. define("tinymce/WindowManager", [
  20479. "tinymce/ui/Window",
  20480. "tinymce/ui/MessageBox"
  20481. ], function(Window, MessageBox) {
  20482. return function(editor) {
  20483. var self = this, windows = [];
  20484. function getTopMostWindow() {
  20485. if (windows.length) {
  20486. return windows[windows.length - 1];
  20487. }
  20488. }
  20489. self.windows = windows;
  20490. /**
  20491. * Opens a new window.
  20492. *
  20493. * @method open
  20494. * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
  20495. * @option {String} title Window title.
  20496. * @option {String} file URL of the file to open in the window.
  20497. * @option {Number} width Width in pixels.
  20498. * @option {Number} height Height in pixels.
  20499. * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
  20500. * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
  20501. * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
  20502. * larger than the popup size specified).
  20503. */
  20504. self.open = function(args, params) {
  20505. var win;
  20506. editor.editorManager.activeEditor = editor;
  20507. args.title = args.title || ' ';
  20508. // Handle URL
  20509. args.url = args.url || args.file; // Legacy
  20510. if (args.url) {
  20511. args.width = parseInt(args.width || 320, 10);
  20512. args.height = parseInt(args.height || 240, 10);
  20513. }
  20514. // Handle body
  20515. if (args.body) {
  20516. args.items = {
  20517. defaults: args.defaults,
  20518. type: args.bodyType || 'form',
  20519. items: args.body
  20520. };
  20521. }
  20522. if (!args.url && !args.buttons) {
  20523. args.buttons = [
  20524. {text: 'Ok', subtype: 'primary', onclick: function() {
  20525. win.find('form')[0].submit();
  20526. }},
  20527. {text: 'Cancel', onclick: function() {
  20528. win.close();
  20529. }}
  20530. ];
  20531. }
  20532. win = new Window(args);
  20533. windows.push(win);
  20534. win.on('close', function() {
  20535. var i = windows.length;
  20536. while (i--) {
  20537. if (windows[i] === win) {
  20538. windows.splice(i, 1);
  20539. }
  20540. }
  20541. editor.focus();
  20542. });
  20543. // Handle data
  20544. if (args.data) {
  20545. win.on('postRender', function() {
  20546. this.find('*').each(function(ctrl) {
  20547. var name = ctrl.name();
  20548. if (name in args.data) {
  20549. ctrl.value(args.data[name]);
  20550. }
  20551. });
  20552. });
  20553. }
  20554. // store args and parameters
  20555. win.features = args || {};
  20556. win.params = params || {};
  20557. // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
  20558. editor.nodeChanged();
  20559. return win.renderTo(document.body).reflow();
  20560. };
  20561. /**
  20562. * Creates a alert dialog. Please don't use the blocking behavior of this
  20563. * native version use the callback method instead then it can be extended.
  20564. *
  20565. * @method alert
  20566. * @param {String} message Text to display in the new alert dialog.
  20567. * @param {function} callback Callback function to be executed after the user has selected ok.
  20568. * @param {Object} scope Optional scope to execute the callback in.
  20569. * @example
  20570. * // Displays an alert box using the active editors window manager instance
  20571. * tinymce.activeEditor.windowManager.alert('Hello world!');
  20572. */
  20573. self.alert = function(message, callback, scope) {
  20574. MessageBox.alert(message, function() {
  20575. if (callback) {
  20576. callback.call(scope || this);
  20577. } else {
  20578. editor.focus();
  20579. }
  20580. });
  20581. };
  20582. /**
  20583. * Creates a confirm dialog. Please don't use the blocking behavior of this
  20584. * native version use the callback method instead then it can be extended.
  20585. *
  20586. * @method confirm
  20587. * @param {String} messageText to display in the new confirm dialog.
  20588. * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
  20589. * @param {Object} scope Optional scope to execute the callback in.
  20590. * @example
  20591. * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
  20592. * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
  20593. * if (s)
  20594. * tinymce.activeEditor.windowManager.alert("Ok");
  20595. * else
  20596. * tinymce.activeEditor.windowManager.alert("Cancel");
  20597. * });
  20598. */
  20599. self.confirm = function(message, callback, scope) {
  20600. MessageBox.confirm(message, function(state) {
  20601. callback.call(scope || this, state);
  20602. });
  20603. };
  20604. /**
  20605. * Closes the top most window.
  20606. *
  20607. * @method close
  20608. */
  20609. self.close = function() {
  20610. if (getTopMostWindow()) {
  20611. getTopMostWindow().close();
  20612. }
  20613. };
  20614. /**
  20615. * Returns the params of the last window open call. This can be used in iframe based
  20616. * dialog to get params passed from the tinymce plugin.
  20617. *
  20618. * @example
  20619. * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
  20620. *
  20621. * @method getParams
  20622. * @return {Object} Name/value object with parameters passed from windowManager.open call.
  20623. */
  20624. self.getParams = function() {
  20625. return getTopMostWindow() ? getTopMostWindow().params : null;
  20626. };
  20627. /**
  20628. * Sets the params of the last opened window.
  20629. *
  20630. * @method setParams
  20631. * @param {Object} params Params object to set for the last opened window.
  20632. */
  20633. self.setParams = function(params) {
  20634. if (getTopMostWindow()) {
  20635. getTopMostWindow().params = params;
  20636. }
  20637. };
  20638. };
  20639. });
  20640. // Included from: js/tinymce/classes/util/Quirks.js
  20641. /**
  20642. * Quirks.js
  20643. *
  20644. * Copyright, Moxiecode Systems AB
  20645. * Released under LGPL License.
  20646. *
  20647. * License: http://www.tinymce.com/license
  20648. * Contributing: http://www.tinymce.com/contributing
  20649. *
  20650. * @ignore-file
  20651. */
  20652. /**
  20653. * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
  20654. *
  20655. * @class tinymce.util.Quirks
  20656. */
  20657. define("tinymce/util/Quirks", [
  20658. "tinymce/util/VK",
  20659. "tinymce/dom/RangeUtils",
  20660. "tinymce/html/Node",
  20661. "tinymce/html/Entities",
  20662. "tinymce/Env",
  20663. "tinymce/util/Tools"
  20664. ], function(VK, RangeUtils, Node, Entities, Env, Tools) {
  20665. return function(editor) {
  20666. var each = Tools.each;
  20667. var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
  20668. settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
  20669. var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
  20670. /**
  20671. * Executes a command with a specific state this can be to enable/disable browser editing features.
  20672. */
  20673. function setEditorCommandState(cmd, state) {
  20674. try {
  20675. editor.getDoc().execCommand(cmd, false, state);
  20676. } catch (ex) {
  20677. // Ignore
  20678. }
  20679. }
  20680. /**
  20681. * Returns current IE document mode.
  20682. */
  20683. function getDocumentMode() {
  20684. var documentMode = editor.getDoc().documentMode;
  20685. return documentMode ? documentMode : 6;
  20686. }
  20687. /**
  20688. * Returns true/false if the event is prevented or not.
  20689. *
  20690. * @private
  20691. * @param {Event} e Event object.
  20692. * @return {Boolean} true/false if the event is prevented or not.
  20693. */
  20694. function isDefaultPrevented(e) {
  20695. return e.isDefaultPrevented();
  20696. }
  20697. /**
  20698. * Fixes a WebKit bug when deleting contents using backspace or delete key.
  20699. * WebKit will produce a span element if you delete across two block elements.
  20700. *
  20701. * Example:
  20702. * <h1>a</h1><p>|b</p>
  20703. *
  20704. * Will produce this on backspace:
  20705. * <h1>a<span style="<all runtime styles>">b</span></p>
  20706. *
  20707. * This fixes the backspace to produce:
  20708. * <h1>a|b</p>
  20709. *
  20710. * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
  20711. *
  20712. * This fixes the following delete scenarios:
  20713. * 1. Delete by pressing backspace key.
  20714. * 2. Delete by pressing delete key.
  20715. * 3. Delete by pressing backspace key with ctrl/cmd (Word delete).
  20716. * 4. Delete by pressing delete key with ctrl/cmd (Word delete).
  20717. * 5. Delete by drag/dropping contents inside the editor.
  20718. * 6. Delete by using Cut Ctrl+X/Cmd+X.
  20719. * 7. Delete by selecting contents and writing a character.'
  20720. *
  20721. * This code is a ugly hack since writing full custom delete logic for just this bug
  20722. * fix seemed like a huge task. I hope we can remove this before the year 2030.
  20723. */
  20724. function cleanupStylesWhenDeleting() {
  20725. var doc = editor.getDoc(), urlPrefix = 'data:text/mce-internal,';
  20726. var MutationObserver = window.MutationObserver, olderWebKit;
  20727. // Add mini polyfill for older WebKits
  20728. // TODO: Remove this when old Safari versions gets updated
  20729. if (!MutationObserver) {
  20730. olderWebKit = true;
  20731. MutationObserver = function() {
  20732. var records = [], target;
  20733. function nodeInsert(e) {
  20734. var target = e.relatedNode || e.target;
  20735. records.push({target: target, addedNodes: [target]});
  20736. }
  20737. function attrModified(e) {
  20738. var target = e.relatedNode || e.target;
  20739. records.push({target: target, attributeName: e.attrName});
  20740. }
  20741. this.observe = function(node) {
  20742. target = node;
  20743. target.addEventListener('DOMSubtreeModified', nodeInsert, false);
  20744. target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
  20745. target.addEventListener('DOMNodeInserted', nodeInsert, false);
  20746. target.addEventListener('DOMAttrModified', attrModified, false);
  20747. };
  20748. this.disconnect = function() {
  20749. target.removeEventListener('DOMNodeInserted', nodeInsert);
  20750. target.removeEventListener('DOMAttrModified', attrModified);
  20751. target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
  20752. };
  20753. this.takeRecords = function() {
  20754. return records;
  20755. };
  20756. };
  20757. }
  20758. function customDelete(isForward) {
  20759. var mutationObserver = new MutationObserver(function() {});
  20760. Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
  20761. // Mark existing spans
  20762. if (elm.tagName == 'SPAN') {
  20763. elm.setAttribute('mce-data-marked', 1);
  20764. }
  20765. // Make sure all elements has a data-mce-style attribute
  20766. if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
  20767. editor.dom.setAttrib(elm, 'style', elm.getAttribute('style'));
  20768. }
  20769. });
  20770. // Observe added nodes and style attribute changes
  20771. mutationObserver.observe(editor.getDoc(), {
  20772. childList: true,
  20773. attributes: true,
  20774. subtree: true,
  20775. attributeFilter: ['style']
  20776. });
  20777. editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
  20778. var rng = editor.selection.getRng();
  20779. var caretElement = rng.startContainer.parentNode;
  20780. Tools.each(mutationObserver.takeRecords(), function(record) {
  20781. // Restore style attribute to previous value
  20782. if (record.attributeName == "style") {
  20783. var oldValue = record.target.getAttribute('data-mce-style');
  20784. if (oldValue) {
  20785. record.target.setAttribute("style", oldValue);
  20786. } else {
  20787. record.target.removeAttribute("style");
  20788. }
  20789. }
  20790. // Remove all spans that isn't maked and retain selection
  20791. Tools.each(record.addedNodes, function(node) {
  20792. if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
  20793. var offset, container;
  20794. if (node == caretElement) {
  20795. offset = rng.startOffset;
  20796. container = node.firstChild;
  20797. }
  20798. dom.remove(node, true);
  20799. if (container) {
  20800. rng.setStart(container, offset);
  20801. rng.setEnd(container, offset);
  20802. editor.selection.setRng(rng);
  20803. }
  20804. }
  20805. });
  20806. });
  20807. mutationObserver.disconnect();
  20808. // Remove any left over marks
  20809. Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
  20810. span.removeAttribute('mce-data-marked');
  20811. });
  20812. }
  20813. editor.on('keydown', function(e) {
  20814. var isForward = e.keyCode == DELETE, isMeta = VK.metaKeyPressed(e);
  20815. if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
  20816. var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
  20817. // Ignore non meta delete in the where there is text before/after the caret
  20818. if (!isMeta && rng.collapsed && container.nodeType == 3) {
  20819. if (isForward ? offset < container.data.length : offset > 0) {
  20820. return;
  20821. }
  20822. }
  20823. e.preventDefault();
  20824. if (isMeta) {
  20825. editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", "word");
  20826. }
  20827. customDelete(isForward);
  20828. }
  20829. });
  20830. editor.on('keypress', function(e) {
  20831. if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode && !VK.metaKeyPressed(e)) {
  20832. e.preventDefault();
  20833. customDelete(true);
  20834. editor.selection.setContent(String.fromCharCode(e.charCode));
  20835. }
  20836. });
  20837. editor.addCommand('Delete', function() {
  20838. customDelete();
  20839. });
  20840. editor.addCommand('ForwardDelete', function() {
  20841. customDelete(true);
  20842. });
  20843. // Older WebKits doesn't properly handle the clipboard so we can't add the rest
  20844. if (olderWebKit) {
  20845. return;
  20846. }
  20847. editor.on('dragstart', function(e) {
  20848. // Safari doesn't support custom dataTransfer items so we can only use URL and Text
  20849. e.dataTransfer.setData('URL', 'data:text/mce-internal,' + escape(editor.selection.getContent()));
  20850. });
  20851. editor.on('drop', function(e) {
  20852. if (!isDefaultPrevented(e)) {
  20853. var internalContent = e.dataTransfer.getData('URL');
  20854. if (!internalContent || internalContent.indexOf(urlPrefix) == -1 || !doc.caretRangeFromPoint) {
  20855. return;
  20856. }
  20857. internalContent = unescape(internalContent.substr(urlPrefix.length));
  20858. if (doc.caretRangeFromPoint) {
  20859. e.preventDefault();
  20860. customDelete();
  20861. editor.selection.setRng(doc.caretRangeFromPoint(e.x, e.y));
  20862. editor.insertContent(internalContent);
  20863. }
  20864. }
  20865. });
  20866. editor.on('cut', function(e) {
  20867. if (!isDefaultPrevented(e) && e.clipboardData) {
  20868. e.preventDefault();
  20869. e.clipboardData.clearData();
  20870. e.clipboardData.setData('text/html', editor.selection.getContent());
  20871. e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
  20872. customDelete(true);
  20873. }
  20874. });
  20875. }
  20876. /**
  20877. * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
  20878. *
  20879. * For example:
  20880. * <p><b>|</b></p>
  20881. *
  20882. * Or:
  20883. * <h1>|</h1>
  20884. *
  20885. * Or:
  20886. * [<h1></h1>]
  20887. */
  20888. function emptyEditorWhenDeleting() {
  20889. function serializeRng(rng) {
  20890. var body = dom.create("body");
  20891. var contents = rng.cloneContents();
  20892. body.appendChild(contents);
  20893. return selection.serializer.serialize(body, {format: 'html'});
  20894. }
  20895. function allContentsSelected(rng) {
  20896. if (!rng.setStart) {
  20897. if (rng.item) {
  20898. return false;
  20899. }
  20900. var bodyRng = rng.duplicate();
  20901. bodyRng.moveToElementText(editor.getBody());
  20902. return RangeUtils.compareRanges(rng, bodyRng);
  20903. }
  20904. var selection = serializeRng(rng);
  20905. var allRng = dom.createRng();
  20906. allRng.selectNode(editor.getBody());
  20907. var allSelection = serializeRng(allRng);
  20908. return selection === allSelection;
  20909. }
  20910. editor.on('keydown', function(e) {
  20911. var keyCode = e.keyCode, isCollapsed, body;
  20912. // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
  20913. if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
  20914. isCollapsed = editor.selection.isCollapsed();
  20915. body = editor.getBody();
  20916. // Selection is collapsed but the editor isn't empty
  20917. if (isCollapsed && !dom.isEmpty(body)) {
  20918. return;
  20919. }
  20920. // Selection isn't collapsed but not all the contents is selected
  20921. if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
  20922. return;
  20923. }
  20924. // Manually empty the editor
  20925. e.preventDefault();
  20926. editor.setContent('');
  20927. if (body.firstChild && dom.isBlock(body.firstChild)) {
  20928. editor.selection.setCursorLocation(body.firstChild, 0);
  20929. } else {
  20930. editor.selection.setCursorLocation(body, 0);
  20931. }
  20932. editor.nodeChanged();
  20933. }
  20934. });
  20935. }
  20936. /**
  20937. * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
  20938. * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
  20939. * This selects the whole body so that backspace/delete logic will delete everything
  20940. */
  20941. function selectAll() {
  20942. editor.on('keydown', function(e) {
  20943. if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
  20944. e.preventDefault();
  20945. editor.execCommand('SelectAll');
  20946. }
  20947. });
  20948. }
  20949. /**
  20950. * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
  20951. * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
  20952. *
  20953. * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
  20954. * you enter a character into the editor.
  20955. *
  20956. * It also happens when the first focus in made to the body.
  20957. *
  20958. * See: https://bugs.webkit.org/show_bug.cgi?id=83566
  20959. */
  20960. function inputMethodFocus() {
  20961. if (!editor.settings.content_editable) {
  20962. // Case 1 IME doesn't initialize if you focus the document
  20963. dom.bind(editor.getDoc(), 'focusin', function() {
  20964. selection.setRng(selection.getRng());
  20965. });
  20966. // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
  20967. dom.bind(editor.getDoc(), 'mousedown', function(e) {
  20968. if (e.target == editor.getDoc().documentElement) {
  20969. editor.getBody().focus();
  20970. selection.setRng(selection.getRng());
  20971. }
  20972. });
  20973. }
  20974. }
  20975. /**
  20976. * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
  20977. * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
  20978. * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
  20979. * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
  20980. * browsers.
  20981. *
  20982. * It also fixes a bug on Firefox where it's impossible to delete HR elements.
  20983. */
  20984. function removeHrOnBackspace() {
  20985. editor.on('keydown', function(e) {
  20986. if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  20987. if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  20988. var node = selection.getNode();
  20989. var previousSibling = node.previousSibling;
  20990. if (node.nodeName == 'HR') {
  20991. dom.remove(node);
  20992. e.preventDefault();
  20993. return;
  20994. }
  20995. if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
  20996. dom.remove(previousSibling);
  20997. e.preventDefault();
  20998. }
  20999. }
  21000. }
  21001. });
  21002. }
  21003. /**
  21004. * Firefox 3.x has an issue where the body element won't get proper focus if you click out
  21005. * side it's rectangle.
  21006. */
  21007. function focusBody() {
  21008. // Fix for a focus bug in FF 3.x where the body element
  21009. // wouldn't get proper focus if the user clicked on the HTML element
  21010. if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
  21011. editor.on('mousedown', function(e) {
  21012. if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
  21013. var body = editor.getBody();
  21014. // Blur the body it's focused but not correctly focused
  21015. body.blur();
  21016. // Refocus the body after a little while
  21017. setTimeout(function() {
  21018. body.focus();
  21019. }, 0);
  21020. }
  21021. });
  21022. }
  21023. }
  21024. /**
  21025. * WebKit has a bug where it isn't possible to select image, hr or anchor elements
  21026. * by clicking on them so we need to fake that.
  21027. */
  21028. function selectControlElements() {
  21029. editor.on('click', function(e) {
  21030. e = e.target;
  21031. // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
  21032. // WebKit can't even do simple things like selecting an image
  21033. // Needs tobe the setBaseAndExtend or it will fail to select floated images
  21034. if (/^(IMG|HR)$/.test(e.nodeName)) {
  21035. selection.getSel().setBaseAndExtent(e, 0, e, 1);
  21036. }
  21037. if (e.nodeName == 'A' && dom.hasClass(e, 'mce-item-anchor')) {
  21038. selection.select(e);
  21039. }
  21040. editor.nodeChanged();
  21041. });
  21042. }
  21043. /**
  21044. * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
  21045. *
  21046. * Fixes do backspace/delete on this:
  21047. * <p>bla[ck</p><p style="color:red">r]ed</p>
  21048. *
  21049. * Would become:
  21050. * <p>bla|ed</p>
  21051. *
  21052. * Instead of:
  21053. * <p style="color:red">bla|ed</p>
  21054. */
  21055. function removeStylesWhenDeletingAcrossBlockElements() {
  21056. function getAttributeApplyFunction() {
  21057. var template = dom.getAttribs(selection.getStart().cloneNode(false));
  21058. return function() {
  21059. var target = selection.getStart();
  21060. if (target !== editor.getBody()) {
  21061. dom.setAttrib(target, "style", null);
  21062. each(template, function(attr) {
  21063. target.setAttributeNode(attr.cloneNode(true));
  21064. });
  21065. }
  21066. };
  21067. }
  21068. function isSelectionAcrossElements() {
  21069. return !selection.isCollapsed() &&
  21070. dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
  21071. }
  21072. editor.on('keypress', function(e) {
  21073. var applyAttributes;
  21074. if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
  21075. applyAttributes = getAttributeApplyFunction();
  21076. editor.getDoc().execCommand('delete', false, null);
  21077. applyAttributes();
  21078. e.preventDefault();
  21079. return false;
  21080. }
  21081. });
  21082. dom.bind(editor.getDoc(), 'cut', function(e) {
  21083. var applyAttributes;
  21084. if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
  21085. applyAttributes = getAttributeApplyFunction();
  21086. setTimeout(function() {
  21087. applyAttributes();
  21088. }, 0);
  21089. }
  21090. });
  21091. }
  21092. /**
  21093. * Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5. It only fires the nodeChange
  21094. * event every 50ms since it would other wise update the UI when you type and it hogs the CPU.
  21095. */
  21096. function selectionChangeNodeChanged() {
  21097. var lastRng, selectionTimer;
  21098. editor.on('selectionchange', function() {
  21099. if (selectionTimer) {
  21100. clearTimeout(selectionTimer);
  21101. selectionTimer = 0;
  21102. }
  21103. selectionTimer = window.setTimeout(function() {
  21104. if (editor.removed) {
  21105. return;
  21106. }
  21107. var rng = selection.getRng();
  21108. // Compare the ranges to see if it was a real change or not
  21109. if (!lastRng || !RangeUtils.compareRanges(rng, lastRng)) {
  21110. editor.nodeChanged();
  21111. lastRng = rng;
  21112. }
  21113. }, 50);
  21114. });
  21115. }
  21116. /**
  21117. * Screen readers on IE needs to have the role application set on the body.
  21118. */
  21119. function ensureBodyHasRoleApplication() {
  21120. document.body.setAttribute("role", "application");
  21121. }
  21122. /**
  21123. * Backspacing into a table behaves differently depending upon browser type.
  21124. * Therefore, disable Backspace when cursor immediately follows a table.
  21125. */
  21126. function disableBackspaceIntoATable() {
  21127. editor.on('keydown', function(e) {
  21128. if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
  21129. if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
  21130. var previousSibling = selection.getNode().previousSibling;
  21131. if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
  21132. e.preventDefault();
  21133. return false;
  21134. }
  21135. }
  21136. }
  21137. });
  21138. }
  21139. /**
  21140. * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
  21141. * logic adds a \n before the BR so that it will get rendered.
  21142. */
  21143. function addNewLinesBeforeBrInPre() {
  21144. // IE8+ rendering mode does the right thing with BR in PRE
  21145. if (getDocumentMode() > 7) {
  21146. return;
  21147. }
  21148. // Enable display: none in area and add a specific class that hides all BR elements in PRE to
  21149. // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
  21150. setEditorCommandState('RespectVisibilityInDesign', true);
  21151. editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
  21152. dom.addClass(editor.getBody(), 'mceHideBrInPre');
  21153. // Adds a \n before all BR elements in PRE to get them visual
  21154. parser.addNodeFilter('pre', function(nodes) {
  21155. var i = nodes.length, brNodes, j, brElm, sibling;
  21156. while (i--) {
  21157. brNodes = nodes[i].getAll('br');
  21158. j = brNodes.length;
  21159. while (j--) {
  21160. brElm = brNodes[j];
  21161. // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
  21162. sibling = brElm.prev;
  21163. if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
  21164. sibling.value += '\n';
  21165. } else {
  21166. brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
  21167. }
  21168. }
  21169. }
  21170. });
  21171. // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
  21172. serializer.addNodeFilter('pre', function(nodes) {
  21173. var i = nodes.length, brNodes, j, brElm, sibling;
  21174. while (i--) {
  21175. brNodes = nodes[i].getAll('br');
  21176. j = brNodes.length;
  21177. while (j--) {
  21178. brElm = brNodes[j];
  21179. sibling = brElm.prev;
  21180. if (sibling && sibling.type == 3) {
  21181. sibling.value = sibling.value.replace(/\r?\n$/, '');
  21182. }
  21183. }
  21184. }
  21185. });
  21186. }
  21187. /**
  21188. * Moves style width/height to attribute width/height when the user resizes an image on IE.
  21189. */
  21190. function removePreSerializedStylesWhenSelectingControls() {
  21191. dom.bind(editor.getBody(), 'mouseup', function() {
  21192. var value, node = selection.getNode();
  21193. // Moved styles to attributes on IMG eements
  21194. if (node.nodeName == 'IMG') {
  21195. // Convert style width to width attribute
  21196. if ((value = dom.getStyle(node, 'width'))) {
  21197. dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
  21198. dom.setStyle(node, 'width', '');
  21199. }
  21200. // Convert style height to height attribute
  21201. if ((value = dom.getStyle(node, 'height'))) {
  21202. dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
  21203. dom.setStyle(node, 'height', '');
  21204. }
  21205. }
  21206. });
  21207. }
  21208. /**
  21209. * Removes a blockquote when backspace is pressed at the beginning of it.
  21210. *
  21211. * For example:
  21212. * <blockquote><p>|x</p></blockquote>
  21213. *
  21214. * Becomes:
  21215. * <p>|x</p>
  21216. */
  21217. function removeBlockQuoteOnBackSpace() {
  21218. // Add block quote deletion handler
  21219. editor.on('keydown', function(e) {
  21220. var rng, container, offset, root, parent;
  21221. if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
  21222. return;
  21223. }
  21224. rng = selection.getRng();
  21225. container = rng.startContainer;
  21226. offset = rng.startOffset;
  21227. root = dom.getRoot();
  21228. parent = container;
  21229. if (!rng.collapsed || offset !== 0) {
  21230. return;
  21231. }
  21232. while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
  21233. parent = parent.parentNode;
  21234. }
  21235. // Is the cursor at the beginning of a blockquote?
  21236. if (parent.tagName === 'BLOCKQUOTE') {
  21237. // Remove the blockquote
  21238. editor.formatter.toggle('blockquote', null, parent);
  21239. // Move the caret to the beginning of container
  21240. rng = dom.createRng();
  21241. rng.setStart(container, 0);
  21242. rng.setEnd(container, 0);
  21243. selection.setRng(rng);
  21244. }
  21245. });
  21246. }
  21247. /**
  21248. * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
  21249. */
  21250. function setGeckoEditingOptions() {
  21251. function setOpts() {
  21252. editor._refreshContentEditable();
  21253. setEditorCommandState("StyleWithCSS", false);
  21254. setEditorCommandState("enableInlineTableEditing", false);
  21255. if (!settings.object_resizing) {
  21256. setEditorCommandState("enableObjectResizing", false);
  21257. }
  21258. }
  21259. if (!settings.readonly) {
  21260. editor.on('BeforeExecCommand MouseDown', setOpts);
  21261. }
  21262. }
  21263. /**
  21264. * Fixes a gecko link bug, when a link is placed at the end of block elements there is
  21265. * no way to move the caret behind the link. This fix adds a bogus br element after the link.
  21266. *
  21267. * For example this:
  21268. * <p><b><a href="#">x</a></b></p>
  21269. *
  21270. * Becomes this:
  21271. * <p><b><a href="#">x</a></b><br></p>
  21272. */
  21273. function addBrAfterLastLinks() {
  21274. function fixLinks() {
  21275. each(dom.select('a'), function(node) {
  21276. var parentNode = node.parentNode, root = dom.getRoot();
  21277. if (parentNode.lastChild === node) {
  21278. while (parentNode && !dom.isBlock(parentNode)) {
  21279. if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
  21280. return;
  21281. }
  21282. parentNode = parentNode.parentNode;
  21283. }
  21284. dom.add(parentNode, 'br', {'data-mce-bogus': 1});
  21285. }
  21286. });
  21287. }
  21288. editor.on('SetContent ExecCommand', function(e) {
  21289. if (e.type == "setcontent" || e.command === 'mceInsertLink') {
  21290. fixLinks();
  21291. }
  21292. });
  21293. }
  21294. /**
  21295. * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
  21296. * default we want to change that behavior.
  21297. */
  21298. function setDefaultBlockType() {
  21299. if (settings.forced_root_block) {
  21300. editor.on('init', function() {
  21301. setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
  21302. });
  21303. }
  21304. }
  21305. /**
  21306. * Removes ghost selections from images/tables on Gecko.
  21307. */
  21308. function removeGhostSelection() {
  21309. editor.on('Undo Redo SetContent', function(e) {
  21310. if (!e.initial) {
  21311. editor.execCommand('mceRepaint');
  21312. }
  21313. });
  21314. }
  21315. /**
  21316. * Deletes the selected image on IE instead of navigating to previous page.
  21317. */
  21318. function deleteControlItemOnBackSpace() {
  21319. editor.on('keydown', function(e) {
  21320. var rng;
  21321. if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
  21322. rng = editor.getDoc().selection.createRange();
  21323. if (rng && rng.item) {
  21324. e.preventDefault();
  21325. editor.undoManager.beforeChange();
  21326. dom.remove(rng.item(0));
  21327. editor.undoManager.add();
  21328. }
  21329. }
  21330. });
  21331. }
  21332. /**
  21333. * IE10 doesn't properly render block elements with the right height until you add contents to them.
  21334. * This fixes that by adding a padding-right to all empty text block elements.
  21335. * See: https://connect.microsoft.com/IE/feedback/details/743881
  21336. */
  21337. function renderEmptyBlocksFix() {
  21338. var emptyBlocksCSS;
  21339. // IE10+
  21340. if (getDocumentMode() >= 10) {
  21341. emptyBlocksCSS = '';
  21342. each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
  21343. emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
  21344. });
  21345. editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
  21346. }
  21347. }
  21348. /**
  21349. * Old IE versions can't retain contents within noscript elements so this logic will store the contents
  21350. * as a attribute and the insert that value as it's raw text when the DOM is serialized.
  21351. */
  21352. function keepNoScriptContents() {
  21353. if (getDocumentMode() < 9) {
  21354. parser.addNodeFilter('noscript', function(nodes) {
  21355. var i = nodes.length, node, textNode;
  21356. while (i--) {
  21357. node = nodes[i];
  21358. textNode = node.firstChild;
  21359. if (textNode) {
  21360. node.attr('data-mce-innertext', textNode.value);
  21361. }
  21362. }
  21363. });
  21364. serializer.addNodeFilter('noscript', function(nodes) {
  21365. var i = nodes.length, node, textNode, value;
  21366. while (i--) {
  21367. node = nodes[i];
  21368. textNode = nodes[i].firstChild;
  21369. if (textNode) {
  21370. textNode.value = Entities.decode(textNode.value);
  21371. } else {
  21372. // Old IE can't retain noscript value so an attribute is used to store it
  21373. value = node.attributes.map['data-mce-innertext'];
  21374. if (value) {
  21375. node.attr('data-mce-innertext', null);
  21376. textNode = new Node('#text', 3);
  21377. textNode.value = value;
  21378. textNode.raw = true;
  21379. node.append(textNode);
  21380. }
  21381. }
  21382. }
  21383. });
  21384. }
  21385. }
  21386. /**
  21387. * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
  21388. */
  21389. function fixCaretSelectionOfDocumentElementOnIe() {
  21390. var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
  21391. // Return range from point or null if it failed
  21392. function rngFromPoint(x, y) {
  21393. var rng = body.createTextRange();
  21394. try {
  21395. rng.moveToPoint(x, y);
  21396. } catch (ex) {
  21397. // IE sometimes throws and exception, so lets just ignore it
  21398. rng = null;
  21399. }
  21400. return rng;
  21401. }
  21402. // Fires while the selection is changing
  21403. function selectionChange(e) {
  21404. var pointRng;
  21405. // Check if the button is down or not
  21406. if (e.button) {
  21407. // Create range from mouse position
  21408. pointRng = rngFromPoint(e.x, e.y);
  21409. if (pointRng) {
  21410. // Check if pointRange is before/after selection then change the endPoint
  21411. if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
  21412. pointRng.setEndPoint('StartToStart', startRng);
  21413. } else {
  21414. pointRng.setEndPoint('EndToEnd', startRng);
  21415. }
  21416. pointRng.select();
  21417. }
  21418. } else {
  21419. endSelection();
  21420. }
  21421. }
  21422. // Removes listeners
  21423. function endSelection() {
  21424. var rng = doc.selection.createRange();
  21425. // If the range is collapsed then use the last start range
  21426. if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
  21427. startRng.select();
  21428. }
  21429. dom.unbind(doc, 'mouseup', endSelection);
  21430. dom.unbind(doc, 'mousemove', selectionChange);
  21431. startRng = started = 0;
  21432. }
  21433. // Make HTML element unselectable since we are going to handle selection by hand
  21434. doc.documentElement.unselectable = true;
  21435. // Detect when user selects outside BODY
  21436. dom.bind(doc, 'mousedown contextmenu', function(e) {
  21437. if (e.target.nodeName === 'HTML') {
  21438. if (started) {
  21439. endSelection();
  21440. }
  21441. // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
  21442. htmlElm = doc.documentElement;
  21443. if (htmlElm.scrollHeight > htmlElm.clientHeight) {
  21444. return;
  21445. }
  21446. started = 1;
  21447. // Setup start position
  21448. startRng = rngFromPoint(e.x, e.y);
  21449. if (startRng) {
  21450. // Listen for selection change events
  21451. dom.bind(doc, 'mouseup', endSelection);
  21452. dom.bind(doc, 'mousemove', selectionChange);
  21453. dom.getRoot().focus();
  21454. startRng.select();
  21455. }
  21456. }
  21457. });
  21458. }
  21459. /**
  21460. * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
  21461. * this fix will lean the caret right into the closest inline element.
  21462. */
  21463. function normalizeSelection() {
  21464. // 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
  21465. editor.on('keyup focusin mouseup', function(e) {
  21466. if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
  21467. selection.normalize();
  21468. }
  21469. }, true);
  21470. }
  21471. /**
  21472. * Forces Gecko to render a broken image icon if it fails to load an image.
  21473. */
  21474. function showBrokenImageIcon() {
  21475. editor.contentStyles.push(
  21476. 'img:-moz-broken {' +
  21477. '-moz-force-broken-image-icon:1;' +
  21478. 'min-width:24px;' +
  21479. 'min-height:24px' +
  21480. '}'
  21481. );
  21482. }
  21483. /**
  21484. * iOS has a bug where it's impossible to type if the document has a touchstart event
  21485. * bound and the user touches the document while having the on screen keyboard visible.
  21486. *
  21487. * The touch event moves the focus to the parent document while having the caret inside the iframe
  21488. * this fix moves the focus back into the iframe document.
  21489. */
  21490. function restoreFocusOnKeyDown() {
  21491. if (!editor.inline) {
  21492. editor.on('keydown', function() {
  21493. if (document.activeElement == document.body) {
  21494. editor.getWin().focus();
  21495. }
  21496. });
  21497. }
  21498. }
  21499. /**
  21500. * IE 11 has an annoying issue where you can't move focus into the editor
  21501. * by clicking on the white area HTML element. We used to be able to to fix this with
  21502. * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
  21503. * object it's not possible anymore. So we need to hack in a ungly CSS to force the
  21504. * body to be at least 150px. If the user clicks the HTML element out side this 150px region
  21505. * we simply move the focus into the first paragraph. Not ideal since you loose the
  21506. * positioning of the caret but goot enough for most cases.
  21507. */
  21508. function bodyHeight() {
  21509. if (!editor.inline) {
  21510. editor.contentStyles.push('body {min-height: 150px}');
  21511. editor.on('click', function(e) {
  21512. if (e.target.nodeName == 'HTML') {
  21513. editor.getBody().focus();
  21514. editor.selection.normalize();
  21515. editor.nodeChanged();
  21516. }
  21517. });
  21518. }
  21519. }
  21520. /**
  21521. * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
  21522. * You might then loose all your work so we need to block that behavior and replace it with our own.
  21523. */
  21524. function blockCmdArrowNavigation() {
  21525. if (Env.mac) {
  21526. editor.on('keydown', function(e) {
  21527. if (VK.metaKeyPressed(e) && (e.keyCode == 37 || e.keyCode == 39)) {
  21528. e.preventDefault();
  21529. editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'word');
  21530. }
  21531. });
  21532. }
  21533. }
  21534. /**
  21535. * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
  21536. */
  21537. function disableAutoUrlDetect() {
  21538. setEditorCommandState("AutoUrlDetect", false);
  21539. }
  21540. /**
  21541. * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
  21542. * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
  21543. * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
  21544. * but not as the lastChild of the body. However is we add a BR element to the body then remove it
  21545. * it doesn't seem to add these BR elements makes sence right?!
  21546. *
  21547. * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
  21548. */
  21549. function doubleTrailingBrElements() {
  21550. if (!editor.inline) {
  21551. editor.on('focus blur', function() {
  21552. var br = editor.dom.create('br');
  21553. editor.getBody().appendChild(br);
  21554. br.parentNode.removeChild(br);
  21555. }, true);
  21556. }
  21557. }
  21558. /**
  21559. * iOS 7.1 introduced two new bugs:
  21560. * 1) It's possible to open links within a contentEditable area by clicking on them.
  21561. * 2) If you hold down the finger it will display the link/image touch callout menu.
  21562. */
  21563. function tapLinksAndImages() {
  21564. editor.on('click', function(e) {
  21565. var elm = e.target;
  21566. do {
  21567. if (elm.tagName === 'A') {
  21568. e.preventDefault();
  21569. return;
  21570. }
  21571. } while ((elm = elm.parentNode));
  21572. });
  21573. editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
  21574. }
  21575. // All browsers
  21576. disableBackspaceIntoATable();
  21577. removeBlockQuoteOnBackSpace();
  21578. emptyEditorWhenDeleting();
  21579. normalizeSelection();
  21580. // WebKit
  21581. if (isWebKit) {
  21582. cleanupStylesWhenDeleting();
  21583. inputMethodFocus();
  21584. selectControlElements();
  21585. setDefaultBlockType();
  21586. // iOS
  21587. if (Env.iOS) {
  21588. selectionChangeNodeChanged();
  21589. restoreFocusOnKeyDown();
  21590. bodyHeight();
  21591. tapLinksAndImages();
  21592. } else {
  21593. selectAll();
  21594. }
  21595. }
  21596. // IE
  21597. if (isIE && Env.ie < 11) {
  21598. removeHrOnBackspace();
  21599. ensureBodyHasRoleApplication();
  21600. addNewLinesBeforeBrInPre();
  21601. removePreSerializedStylesWhenSelectingControls();
  21602. deleteControlItemOnBackSpace();
  21603. renderEmptyBlocksFix();
  21604. keepNoScriptContents();
  21605. fixCaretSelectionOfDocumentElementOnIe();
  21606. }
  21607. if (Env.ie >= 11) {
  21608. bodyHeight();
  21609. doubleTrailingBrElements();
  21610. }
  21611. if (Env.ie) {
  21612. selectAll();
  21613. disableAutoUrlDetect();
  21614. }
  21615. // Gecko
  21616. if (isGecko) {
  21617. removeHrOnBackspace();
  21618. focusBody();
  21619. removeStylesWhenDeletingAcrossBlockElements();
  21620. setGeckoEditingOptions();
  21621. addBrAfterLastLinks();
  21622. removeGhostSelection();
  21623. showBrokenImageIcon();
  21624. blockCmdArrowNavigation();
  21625. }
  21626. };
  21627. });
  21628. // Included from: js/tinymce/classes/util/Observable.js
  21629. /**
  21630. * Observable.js
  21631. *
  21632. * Copyright, Moxiecode Systems AB
  21633. * Released under LGPL License.
  21634. *
  21635. * License: http://www.tinymce.com/license
  21636. * Contributing: http://www.tinymce.com/contributing
  21637. */
  21638. /**
  21639. * This mixin will add event binding logic to classes.
  21640. *
  21641. * @mixin tinymce.util.Observable
  21642. */
  21643. define("tinymce/util/Observable", [
  21644. "tinymce/util/Tools"
  21645. ], function(Tools) {
  21646. var bindingsName = "__bindings";
  21647. var nativeEvents = Tools.makeMap(
  21648. "focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange" +
  21649. " mouseout mouseenter mouseleave keydown keypress keyup contextmenu dragstart dragend dragover draggesture dragdrop drop drag", ' '
  21650. );
  21651. function returnFalse() {
  21652. return false;
  21653. }
  21654. function returnTrue() {
  21655. return true;
  21656. }
  21657. return {
  21658. /**
  21659. * Fires the specified event by name.
  21660. *
  21661. * @method fire
  21662. * @param {String} name Name of the event to fire.
  21663. * @param {tinymce.Event/Object?} args Event arguments.
  21664. * @param {Boolean?} bubble True/false if the event is to be bubbled.
  21665. * @return {tinymce.Event} Event instance passed in converted into tinymce.Event instance.
  21666. * @example
  21667. * instance.fire('event', {...});
  21668. */
  21669. fire: function(name, args, bubble) {
  21670. var self = this, handlers, i, l, callback, parent;
  21671. if (self.removed) {
  21672. return;
  21673. }
  21674. name = name.toLowerCase();
  21675. args = args || {};
  21676. args.type = name;
  21677. // Setup target is there isn't one
  21678. if (!args.target) {
  21679. args.target = self;
  21680. }
  21681. // Add event delegation methods if they are missing
  21682. if (!args.preventDefault) {
  21683. // Add preventDefault method
  21684. args.preventDefault = function() {
  21685. args.isDefaultPrevented = returnTrue;
  21686. };
  21687. // Add stopPropagation
  21688. args.stopPropagation = function() {
  21689. args.isPropagationStopped = returnTrue;
  21690. };
  21691. // Add stopImmediatePropagation
  21692. args.stopImmediatePropagation = function() {
  21693. args.isImmediatePropagationStopped = returnTrue;
  21694. };
  21695. // Add event delegation states
  21696. args.isDefaultPrevented = returnFalse;
  21697. args.isPropagationStopped = returnFalse;
  21698. args.isImmediatePropagationStopped = returnFalse;
  21699. }
  21700. //console.log(name, args);
  21701. if (self[bindingsName]) {
  21702. handlers = self[bindingsName][name];
  21703. if (handlers) {
  21704. for (i = 0, l = handlers.length; i < l; i++) {
  21705. handlers[i] = callback = handlers[i];
  21706. // Stop immediate propagation if needed
  21707. if (args.isImmediatePropagationStopped()) {
  21708. break;
  21709. }
  21710. // If callback returns false then prevent default and stop all propagation
  21711. if (callback.call(self, args) === false) {
  21712. args.preventDefault();
  21713. return args;
  21714. }
  21715. }
  21716. }
  21717. }
  21718. // Bubble event up to parents
  21719. if (bubble !== false && self.parent) {
  21720. parent = self.parent();
  21721. while (parent && !args.isPropagationStopped()) {
  21722. parent.fire(name, args, false);
  21723. parent = parent.parent();
  21724. }
  21725. }
  21726. return args;
  21727. },
  21728. /**
  21729. * Binds an event listener to a specific event by name.
  21730. *
  21731. * @method on
  21732. * @param {String} name Event name or space separated list of events to bind.
  21733. * @param {callback} callback Callback to be executed when the event occurs.
  21734. * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
  21735. * @return {Object} Current class instance.
  21736. * @example
  21737. * instance.on('event', function(e) {
  21738. * // Callback logic
  21739. * });
  21740. */
  21741. on: function(name, callback, prepend) {
  21742. var self = this, bindings, handlers, names, i;
  21743. if (callback === false) {
  21744. callback = function() {
  21745. return false;
  21746. };
  21747. }
  21748. if (callback) {
  21749. names = name.toLowerCase().split(' ');
  21750. i = names.length;
  21751. while (i--) {
  21752. name = names[i];
  21753. bindings = self[bindingsName];
  21754. if (!bindings) {
  21755. bindings = self[bindingsName] = {};
  21756. }
  21757. handlers = bindings[name];
  21758. if (!handlers) {
  21759. handlers = bindings[name] = [];
  21760. if (self.bindNative && nativeEvents[name]) {
  21761. self.bindNative(name);
  21762. }
  21763. }
  21764. if (prepend) {
  21765. handlers.unshift(callback);
  21766. } else {
  21767. handlers.push(callback);
  21768. }
  21769. }
  21770. }
  21771. return self;
  21772. },
  21773. /**
  21774. * Unbinds an event listener to a specific event by name.
  21775. *
  21776. * @method off
  21777. * @param {String?} name Name of the event to unbind.
  21778. * @param {callback?} callback Callback to unbind.
  21779. * @return {Object} Current class instance.
  21780. * @example
  21781. * // Unbind specific callback
  21782. * instance.off('event', handler);
  21783. *
  21784. * // Unbind all listeners by name
  21785. * instance.off('event');
  21786. *
  21787. * // Unbind all events
  21788. * instance.off();
  21789. */
  21790. off: function(name, callback) {
  21791. var self = this, i, bindings = self[bindingsName], handlers, bindingName, names, hi;
  21792. if (bindings) {
  21793. if (name) {
  21794. names = name.toLowerCase().split(' ');
  21795. i = names.length;
  21796. while (i--) {
  21797. name = names[i];
  21798. handlers = bindings[name];
  21799. // Unbind all handlers
  21800. if (!name) {
  21801. for (bindingName in bindings) {
  21802. bindings[name].length = 0;
  21803. }
  21804. return self;
  21805. }
  21806. if (handlers) {
  21807. // Unbind all by name
  21808. if (!callback) {
  21809. handlers.length = 0;
  21810. } else {
  21811. // Unbind specific ones
  21812. hi = handlers.length;
  21813. while (hi--) {
  21814. if (handlers[hi] === callback) {
  21815. handlers.splice(hi, 1);
  21816. }
  21817. }
  21818. }
  21819. if (!handlers.length && self.unbindNative && nativeEvents[name]) {
  21820. self.unbindNative(name);
  21821. delete bindings[name];
  21822. }
  21823. }
  21824. }
  21825. } else {
  21826. if (self.unbindNative) {
  21827. for (name in bindings) {
  21828. self.unbindNative(name);
  21829. }
  21830. }
  21831. self[bindingsName] = [];
  21832. }
  21833. }
  21834. return self;
  21835. },
  21836. hasEventListeners: function(name) {
  21837. var bindings = this[bindingsName];
  21838. name = name.toLowerCase();
  21839. return !(!bindings || !bindings[name] || bindings[name].length === 0);
  21840. }
  21841. };
  21842. });
  21843. // Included from: js/tinymce/classes/Shortcuts.js
  21844. /**
  21845. * Shortcuts.js
  21846. *
  21847. * Copyright, Moxiecode Systems AB
  21848. * Released under LGPL License.
  21849. *
  21850. * License: http://www.tinymce.com/license
  21851. * Contributing: http://www.tinymce.com/contributing
  21852. */
  21853. /**
  21854. * Contains all logic for handling of keyboard shortcuts.
  21855. */
  21856. define("tinymce/Shortcuts", [
  21857. "tinymce/util/Tools",
  21858. "tinymce/Env"
  21859. ], function(Tools, Env) {
  21860. var each = Tools.each, explode = Tools.explode;
  21861. var keyCodeLookup = {
  21862. "f9": 120,
  21863. "f10": 121,
  21864. "f11": 122
  21865. };
  21866. return function(editor) {
  21867. var self = this, shortcuts = {};
  21868. editor.on('keyup keypress keydown', function(e) {
  21869. if (e.altKey || e.ctrlKey || e.metaKey) {
  21870. each(shortcuts, function(shortcut) {
  21871. var ctrlKey = Env.mac ? e.metaKey : e.ctrlKey;
  21872. if (shortcut.ctrl != ctrlKey || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
  21873. return;
  21874. }
  21875. if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
  21876. e.preventDefault();
  21877. if (e.type == "keydown") {
  21878. shortcut.func.call(shortcut.scope);
  21879. }
  21880. return true;
  21881. }
  21882. });
  21883. }
  21884. });
  21885. /**
  21886. * Adds a keyboard shortcut for some command or function.
  21887. *
  21888. * @method addShortcut
  21889. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  21890. * @param {String} desc Text description for the command.
  21891. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  21892. * @param {Object} sc Optional scope to execute the function in.
  21893. * @return {Boolean} true/false state if the shortcut was added or not.
  21894. */
  21895. self.add = function(pattern, desc, cmdFunc, scope) {
  21896. var cmd;
  21897. cmd = cmdFunc;
  21898. if (typeof(cmdFunc) === 'string') {
  21899. cmdFunc = function() {
  21900. editor.execCommand(cmd, false, null);
  21901. };
  21902. } else if (Tools.isArray(cmd)) {
  21903. cmdFunc = function() {
  21904. editor.execCommand(cmd[0], cmd[1], cmd[2]);
  21905. };
  21906. }
  21907. each(explode(pattern.toLowerCase()), function(pattern) {
  21908. var shortcut = {
  21909. func: cmdFunc,
  21910. scope: scope || editor,
  21911. desc: editor.translate(desc),
  21912. alt: false,
  21913. ctrl: false,
  21914. shift: false
  21915. };
  21916. each(explode(pattern, '+'), function(value) {
  21917. switch (value) {
  21918. case 'alt':
  21919. case 'ctrl':
  21920. case 'shift':
  21921. shortcut[value] = true;
  21922. break;
  21923. default:
  21924. shortcut.charCode = value.charCodeAt(0);
  21925. shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
  21926. }
  21927. });
  21928. shortcuts[
  21929. (shortcut.ctrl ? 'ctrl' : '') + ',' +
  21930. (shortcut.alt ? 'alt' : '') + ',' +
  21931. (shortcut.shift ? 'shift' : '') + ',' +
  21932. shortcut.keyCode
  21933. ] = shortcut;
  21934. });
  21935. return true;
  21936. };
  21937. };
  21938. });
  21939. // Included from: js/tinymce/classes/Editor.js
  21940. /**
  21941. * Editor.js
  21942. *
  21943. * Copyright, Moxiecode Systems AB
  21944. * Released under LGPL License.
  21945. *
  21946. * License: http://www.tinymce.com/license
  21947. * Contributing: http://www.tinymce.com/contributing
  21948. */
  21949. /*jshint scripturl:true */
  21950. /**
  21951. * Include the base event class documentation.
  21952. *
  21953. * @include ../../../tools/docs/tinymce.Event.js
  21954. */
  21955. /**
  21956. * This class contains the core logic for a TinyMCE editor.
  21957. *
  21958. * @class tinymce.Editor
  21959. * @mixes tinymce.util.Observable
  21960. * @example
  21961. * // Add a class to all paragraphs in the editor.
  21962. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  21963. *
  21964. * // Gets the current editors selection as text
  21965. * tinymce.activeEditor.selection.getContent({format: 'text'});
  21966. *
  21967. * // Creates a new editor instance
  21968. * var ed = new tinymce.Editor('textareaid', {
  21969. * some_setting: 1
  21970. * }, tinymce.EditorManager);
  21971. *
  21972. * // Select each item the user clicks on
  21973. * ed.on('click', function(e) {
  21974. * ed.selection.select(e.target);
  21975. * });
  21976. *
  21977. * ed.render();
  21978. */
  21979. define("tinymce/Editor", [
  21980. "tinymce/dom/DOMUtils",
  21981. "tinymce/AddOnManager",
  21982. "tinymce/html/Node",
  21983. "tinymce/dom/Serializer",
  21984. "tinymce/html/Serializer",
  21985. "tinymce/dom/Selection",
  21986. "tinymce/Formatter",
  21987. "tinymce/UndoManager",
  21988. "tinymce/EnterKey",
  21989. "tinymce/ForceBlocks",
  21990. "tinymce/EditorCommands",
  21991. "tinymce/util/URI",
  21992. "tinymce/dom/ScriptLoader",
  21993. "tinymce/dom/EventUtils",
  21994. "tinymce/WindowManager",
  21995. "tinymce/html/Schema",
  21996. "tinymce/html/DomParser",
  21997. "tinymce/util/Quirks",
  21998. "tinymce/Env",
  21999. "tinymce/util/Tools",
  22000. "tinymce/util/Observable",
  22001. "tinymce/Shortcuts"
  22002. ], function(
  22003. DOMUtils, AddOnManager, Node, DomSerializer, Serializer,
  22004. Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
  22005. URI, ScriptLoader, EventUtils, WindowManager,
  22006. Schema, DomParser, Quirks, Env, Tools, Observable, Shortcuts
  22007. ) {
  22008. // Shorten these names
  22009. var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
  22010. var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
  22011. var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
  22012. var Event = EventUtils.Event;
  22013. var isGecko = Env.gecko, ie = Env.ie;
  22014. function getEventTarget(editor, eventName) {
  22015. if (eventName == 'selectionchange') {
  22016. return editor.getDoc();
  22017. }
  22018. // Need to bind mousedown/mouseup etc to document not body in iframe mode
  22019. // Since the user might click on the HTML element not the BODY
  22020. if (!editor.inline && /^mouse|click|contextmenu|drop/.test(eventName)) {
  22021. return editor.getDoc();
  22022. }
  22023. return editor.getBody();
  22024. }
  22025. /**
  22026. * Include documentation for all the events.
  22027. *
  22028. * @include ../../../tools/docs/tinymce.Editor.js
  22029. */
  22030. /**
  22031. * Constructs a editor instance by id.
  22032. *
  22033. * @constructor
  22034. * @method Editor
  22035. * @param {String} id Unique id for the editor.
  22036. * @param {Object} settings Settings for the editor.
  22037. * @param {tinymce.EditorManager} editorManager EditorManager instance.
  22038. * @author Moxiecode
  22039. */
  22040. function Editor(id, settings, editorManager) {
  22041. var self = this, documentBaseUrl, baseUri;
  22042. documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
  22043. baseUri = editorManager.baseURI;
  22044. /**
  22045. * Name/value collection with editor settings.
  22046. *
  22047. * @property settings
  22048. * @type Object
  22049. * @example
  22050. * // Get the value of the theme setting
  22051. * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
  22052. */
  22053. self.settings = settings = extend({
  22054. id: id,
  22055. theme: 'modern',
  22056. delta_width: 0,
  22057. delta_height: 0,
  22058. popup_css: '',
  22059. plugins: '',
  22060. document_base_url: documentBaseUrl,
  22061. add_form_submit_trigger: true,
  22062. submit_patch: true,
  22063. add_unload_trigger: true,
  22064. convert_urls: true,
  22065. relative_urls: true,
  22066. remove_script_host: true,
  22067. object_resizing: true,
  22068. doctype: '<!DOCTYPE html>',
  22069. visual: true,
  22070. font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
  22071. // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
  22072. font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
  22073. forced_root_block: 'p',
  22074. hidden_input: true,
  22075. padd_empty_editor: true,
  22076. render_ui: true,
  22077. indentation: '30px',
  22078. inline_styles: true,
  22079. convert_fonts_to_spans: true,
  22080. indent: 'simple',
  22081. indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
  22082. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  22083. indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,' +
  22084. 'tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
  22085. validate: true,
  22086. entity_encoding: 'named',
  22087. url_converter: self.convertURL,
  22088. url_converter_scope: self,
  22089. ie7_compat: true
  22090. }, settings);
  22091. AddOnManager.language = settings.language || 'en';
  22092. AddOnManager.languageLoad = settings.language_load;
  22093. AddOnManager.baseURL = editorManager.baseURL;
  22094. /**
  22095. * Editor instance id, normally the same as the div/textarea that was replaced.
  22096. *
  22097. * @property id
  22098. * @type String
  22099. */
  22100. self.id = settings.id = id;
  22101. /**
  22102. * State to force the editor to return false on a isDirty call.
  22103. *
  22104. * @property isNotDirty
  22105. * @type Boolean
  22106. * @example
  22107. * function ajaxSave() {
  22108. * var ed = tinymce.get('elm1');
  22109. *
  22110. * // Save contents using some XHR call
  22111. * alert(ed.getContent());
  22112. *
  22113. * ed.isNotDirty = true; // Force not dirty state
  22114. * }
  22115. */
  22116. self.isNotDirty = true;
  22117. /**
  22118. * Name/Value object containting plugin instances.
  22119. *
  22120. * @property plugins
  22121. * @type Object
  22122. * @example
  22123. * // Execute a method inside a plugin directly
  22124. * tinymce.activeEditor.plugins.someplugin.someMethod();
  22125. */
  22126. self.plugins = {};
  22127. /**
  22128. * URI object to document configured for the TinyMCE instance.
  22129. *
  22130. * @property documentBaseURI
  22131. * @type tinymce.util.URI
  22132. * @example
  22133. * // Get relative URL from the location of document_base_url
  22134. * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
  22135. *
  22136. * // Get absolute URL from the location of document_base_url
  22137. * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
  22138. */
  22139. self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
  22140. base_uri: baseUri
  22141. });
  22142. /**
  22143. * URI object to current document that holds the TinyMCE editor instance.
  22144. *
  22145. * @property baseURI
  22146. * @type tinymce.util.URI
  22147. * @example
  22148. * // Get relative URL from the location of the API
  22149. * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
  22150. *
  22151. * // Get absolute URL from the location of the API
  22152. * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
  22153. */
  22154. self.baseURI = baseUri;
  22155. /**
  22156. * Array with CSS files to load into the iframe.
  22157. *
  22158. * @property contentCSS
  22159. * @type Array
  22160. */
  22161. self.contentCSS = [];
  22162. /**
  22163. * Array of CSS styles to add to head of document when the editor loads.
  22164. *
  22165. * @property contentStyles
  22166. * @type Array
  22167. */
  22168. self.contentStyles = [];
  22169. // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
  22170. self.shortcuts = new Shortcuts(self);
  22171. // Internal command handler objects
  22172. self.execCommands = {};
  22173. self.queryStateCommands = {};
  22174. self.queryValueCommands = {};
  22175. self.loadedCSS = {};
  22176. self.suffix = editorManager.suffix;
  22177. self.editorManager = editorManager;
  22178. self.inline = settings.inline;
  22179. // Call setup
  22180. editorManager.fire('SetupEditor', self);
  22181. self.execCallback('setup', self);
  22182. }
  22183. Editor.prototype = {
  22184. /**
  22185. * Renderes the editor/adds it to the page.
  22186. *
  22187. * @method render
  22188. */
  22189. render: function() {
  22190. var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
  22191. function readyHandler() {
  22192. DOM.unbind(window, 'ready', readyHandler);
  22193. self.render();
  22194. }
  22195. // Page is not loaded yet, wait for it
  22196. if (!Event.domLoaded) {
  22197. DOM.bind(window, 'ready', readyHandler);
  22198. return;
  22199. }
  22200. // Element not found, then skip initialization
  22201. if (!self.getElement()) {
  22202. return;
  22203. }
  22204. // No editable support old iOS versions etc
  22205. if (!Env.contentEditable) {
  22206. return;
  22207. }
  22208. // Hide target element early to prevent content flashing
  22209. if (!settings.inline) {
  22210. self.orgVisibility = self.getElement().style.visibility;
  22211. self.getElement().style.visibility = 'hidden';
  22212. } else {
  22213. self.inline = true;
  22214. }
  22215. var form = self.getElement().form || DOM.getParent(id, 'form');
  22216. if (form) {
  22217. self.formElement = form;
  22218. // Add hidden input for non input elements inside form elements
  22219. if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
  22220. DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
  22221. self.hasHiddenInput = true;
  22222. }
  22223. // Pass submit/reset from form to editor instance
  22224. self.formEventDelegate = function(e) {
  22225. self.fire(e.type, e);
  22226. };
  22227. DOM.bind(form, 'submit reset', self.formEventDelegate);
  22228. // Reset contents in editor when the form is reset
  22229. self.on('reset', function() {
  22230. self.setContent(self.startContent, {format: 'raw'});
  22231. });
  22232. // Check page uses id="submit" or name="submit" for it's submit button
  22233. if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
  22234. form._mceOldSubmit = form.submit;
  22235. form.submit = function() {
  22236. self.editorManager.triggerSave();
  22237. self.isNotDirty = true;
  22238. return form._mceOldSubmit(form);
  22239. };
  22240. }
  22241. }
  22242. /**
  22243. * Window manager reference, use this to open new windows and dialogs.
  22244. *
  22245. * @property windowManager
  22246. * @type tinymce.WindowManager
  22247. * @example
  22248. * // Shows an alert message
  22249. * tinymce.activeEditor.windowManager.alert('Hello world!');
  22250. *
  22251. * // Opens a new dialog with the file.htm file and the size 320x240
  22252. * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
  22253. * tinymce.activeEditor.windowManager.open({
  22254. * url: 'file.htm',
  22255. * width: 320,
  22256. * height: 240
  22257. * }, {
  22258. * custom_param: 1
  22259. * });
  22260. */
  22261. self.windowManager = new WindowManager(self);
  22262. if (settings.encoding == 'xml') {
  22263. self.on('GetContent', function(e) {
  22264. if (e.save) {
  22265. e.content = DOM.encode(e.content);
  22266. }
  22267. });
  22268. }
  22269. if (settings.add_form_submit_trigger) {
  22270. self.on('submit', function() {
  22271. if (self.initialized) {
  22272. self.save();
  22273. }
  22274. });
  22275. }
  22276. if (settings.add_unload_trigger) {
  22277. self._beforeUnload = function() {
  22278. if (self.initialized && !self.destroyed && !self.isHidden()) {
  22279. self.save({format: 'raw', no_events: true, set_dirty: false});
  22280. }
  22281. };
  22282. self.editorManager.on('BeforeUnload', self._beforeUnload);
  22283. }
  22284. // Load scripts
  22285. function loadScripts() {
  22286. var scriptLoader = ScriptLoader.ScriptLoader;
  22287. if (settings.language && settings.language != 'en' && !settings.language_url) {
  22288. settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
  22289. }
  22290. if (settings.language_url) {
  22291. scriptLoader.add(settings.language_url);
  22292. }
  22293. if (settings.theme && typeof settings.theme != "function" &&
  22294. settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
  22295. var themeUrl = settings.theme_url;
  22296. if (themeUrl) {
  22297. themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
  22298. } else {
  22299. themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
  22300. }
  22301. ThemeManager.load(settings.theme, themeUrl);
  22302. }
  22303. if (Tools.isArray(settings.plugins)) {
  22304. settings.plugins = settings.plugins.join(' ');
  22305. }
  22306. each(settings.external_plugins, function(url, name) {
  22307. PluginManager.load(name, url);
  22308. settings.plugins += ' ' + name;
  22309. });
  22310. each(settings.plugins.split(/[ ,]/), function(plugin) {
  22311. plugin = trim(plugin);
  22312. if (plugin && !PluginManager.urls[plugin]) {
  22313. if (plugin.charAt(0) == '-') {
  22314. plugin = plugin.substr(1, plugin.length);
  22315. var dependencies = PluginManager.dependencies(plugin);
  22316. each(dependencies, function(dep) {
  22317. var defaultSettings = {
  22318. prefix:'plugins/',
  22319. resource: dep,
  22320. suffix:'/plugin' + suffix + '.js'
  22321. };
  22322. dep = PluginManager.createUrl(defaultSettings, dep);
  22323. PluginManager.load(dep.resource, dep);
  22324. });
  22325. } else {
  22326. PluginManager.load(plugin, {
  22327. prefix: 'plugins/',
  22328. resource: plugin,
  22329. suffix: '/plugin' + suffix + '.js'
  22330. });
  22331. }
  22332. }
  22333. });
  22334. scriptLoader.loadQueue(function() {
  22335. if (!self.removed) {
  22336. self.init();
  22337. }
  22338. });
  22339. }
  22340. loadScripts();
  22341. },
  22342. /**
  22343. * Initializes the editor this will be called automatically when
  22344. * all plugins/themes and language packs are loaded by the rendered method.
  22345. * This method will setup the iframe and create the theme and plugin instances.
  22346. *
  22347. * @method init
  22348. */
  22349. init: function() {
  22350. var self = this, settings = self.settings, elm = self.getElement();
  22351. var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
  22352. self.rtl = this.editorManager.i18n.rtl;
  22353. self.editorManager.add(self);
  22354. settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
  22355. /**
  22356. * Reference to the theme instance that was used to generate the UI.
  22357. *
  22358. * @property theme
  22359. * @type tinymce.Theme
  22360. * @example
  22361. * // Executes a method on the theme directly
  22362. * tinymce.activeEditor.theme.someMethod();
  22363. */
  22364. if (settings.theme) {
  22365. if (typeof settings.theme != "function") {
  22366. settings.theme = settings.theme.replace(/-/, '');
  22367. Theme = ThemeManager.get(settings.theme);
  22368. self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
  22369. if (self.theme.init) {
  22370. self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''));
  22371. }
  22372. } else {
  22373. self.theme = settings.theme;
  22374. }
  22375. }
  22376. function initPlugin(plugin) {
  22377. var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
  22378. pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
  22379. plugin = trim(plugin);
  22380. if (Plugin && inArray(initializedPlugins, plugin) === -1) {
  22381. each(PluginManager.dependencies(plugin), function(dep){
  22382. initPlugin(dep);
  22383. });
  22384. pluginInstance = new Plugin(self, pluginUrl);
  22385. self.plugins[plugin] = pluginInstance;
  22386. if (pluginInstance.init) {
  22387. pluginInstance.init(self, pluginUrl);
  22388. initializedPlugins.push(plugin);
  22389. }
  22390. }
  22391. }
  22392. // Create all plugins
  22393. each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
  22394. // Measure box
  22395. if (settings.render_ui && self.theme) {
  22396. self.orgDisplay = elm.style.display;
  22397. if (typeof settings.theme != "function") {
  22398. w = settings.width || elm.style.width || elm.offsetWidth;
  22399. h = settings.height || elm.style.height || elm.offsetHeight;
  22400. minHeight = settings.min_height || 100;
  22401. re = /^[0-9\.]+(|px)$/i;
  22402. if (re.test('' + w)) {
  22403. w = Math.max(parseInt(w, 10), 100);
  22404. }
  22405. if (re.test('' + h)) {
  22406. h = Math.max(parseInt(h, 10), minHeight);
  22407. }
  22408. // Render UI
  22409. o = self.theme.renderUI({
  22410. targetNode: elm,
  22411. width: w,
  22412. height: h,
  22413. deltaWidth: settings.delta_width,
  22414. deltaHeight: settings.delta_height
  22415. });
  22416. // Resize editor
  22417. if (!settings.content_editable) {
  22418. DOM.setStyles(o.sizeContainer || o.editorContainer, {
  22419. wi2dth: w,
  22420. // TODO: Fix this
  22421. h2eight: h
  22422. });
  22423. h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
  22424. if (h < minHeight) {
  22425. h = minHeight;
  22426. }
  22427. }
  22428. } else {
  22429. o = settings.theme(self, elm);
  22430. // Convert element type to id:s
  22431. if (o.editorContainer.nodeType) {
  22432. o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
  22433. }
  22434. // Convert element type to id:s
  22435. if (o.iframeContainer.nodeType) {
  22436. o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
  22437. }
  22438. // Use specified iframe height or the targets offsetHeight
  22439. h = o.iframeHeight || elm.offsetHeight;
  22440. }
  22441. self.editorContainer = o.editorContainer;
  22442. }
  22443. // Load specified content CSS last
  22444. if (settings.content_css) {
  22445. each(explode(settings.content_css), function(u) {
  22446. self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
  22447. });
  22448. }
  22449. // Load specified content CSS last
  22450. if (settings.content_style) {
  22451. self.contentStyles.push(settings.content_style);
  22452. }
  22453. // Content editable mode ends here
  22454. if (settings.content_editable) {
  22455. elm = n = o = null; // Fix IE leak
  22456. return self.initContentBody();
  22457. }
  22458. self.iframeHTML = settings.doctype + '<html><head>';
  22459. // We only need to override paths if we have to
  22460. // IE has a bug where it remove site absolute urls to relative ones if this is specified
  22461. if (settings.document_base_url != self.documentBaseUrl) {
  22462. self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
  22463. }
  22464. // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
  22465. if (!Env.caretAfter && settings.ie7_compat) {
  22466. self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
  22467. }
  22468. self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
  22469. // Load the CSS by injecting them into the HTML this will reduce "flicker"
  22470. for (i = 0; i < self.contentCSS.length; i++) {
  22471. var cssUrl = self.contentCSS[i];
  22472. self.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + cssUrl + '" />';
  22473. self.loadedCSS[cssUrl] = true;
  22474. }
  22475. bodyId = settings.body_id || 'tinymce';
  22476. if (bodyId.indexOf('=') != -1) {
  22477. bodyId = self.getParam('body_id', '', 'hash');
  22478. bodyId = bodyId[self.id] || bodyId;
  22479. }
  22480. bodyClass = settings.body_class || '';
  22481. if (bodyClass.indexOf('=') != -1) {
  22482. bodyClass = self.getParam('body_class', '', 'hash');
  22483. bodyClass = bodyClass[self.id] || '';
  22484. }
  22485. self.iframeHTML += '</head><body id="' + bodyId + '" class="mce-content-body ' + bodyClass + '" ' +
  22486. 'onload="window.parent.tinymce.get(\'' + self.id + '\').fire(\'load\');"><br></body></html>';
  22487. /*eslint no-script-url:0 */
  22488. var domainRelaxUrl = 'javascript:(function(){' +
  22489. 'document.open();document.domain="' + document.domain + '";' +
  22490. 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
  22491. 'document.close();ed.initContentBody(true);})()';
  22492. // Domain relaxing is required since the user has messed around with document.domain
  22493. if (document.domain != location.hostname) {
  22494. url = domainRelaxUrl;
  22495. }
  22496. // Create iframe
  22497. // TODO: ACC add the appropriate description on this.
  22498. n = DOM.add(o.iframeContainer, 'iframe', {
  22499. id: self.id + "_ifr",
  22500. src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
  22501. frameBorder: '0',
  22502. allowTransparency: "true",
  22503. title: self.editorManager.translate(
  22504. "Rich Text Area. Press ALT-F9 for menu. " +
  22505. "Press ALT-F10 for toolbar. Press ALT-0 for help"
  22506. ),
  22507. style: {
  22508. width: '100%',
  22509. height: h,
  22510. display: 'block' // Important for Gecko to render the iframe correctly
  22511. }
  22512. });
  22513. // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
  22514. // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
  22515. if (ie) {
  22516. try {
  22517. self.getDoc();
  22518. } catch (e) {
  22519. n.src = url = domainRelaxUrl;
  22520. }
  22521. }
  22522. self.contentAreaContainer = o.iframeContainer;
  22523. if (o.editorContainer) {
  22524. DOM.get(o.editorContainer).style.display = self.orgDisplay;
  22525. }
  22526. DOM.get(self.id).style.display = 'none';
  22527. DOM.setAttrib(self.id, 'aria-hidden', true);
  22528. if (!url) {
  22529. self.initContentBody();
  22530. }
  22531. elm = n = o = null; // Cleanup
  22532. },
  22533. /**
  22534. * This method get called by the init method ones the iframe is loaded.
  22535. * It will fill the iframe with contents, setups DOM and selection objects for the iframe.
  22536. *
  22537. * @method initContentBody
  22538. * @private
  22539. */
  22540. initContentBody: function(skipWrite) {
  22541. var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), body, contentCssText;
  22542. // Restore visibility on target element
  22543. if (!settings.inline) {
  22544. self.getElement().style.visibility = self.orgVisibility;
  22545. }
  22546. // Setup iframe body
  22547. if (!skipWrite && !settings.content_editable) {
  22548. doc.open();
  22549. doc.write(self.iframeHTML);
  22550. doc.close();
  22551. }
  22552. if (settings.content_editable) {
  22553. self.on('remove', function() {
  22554. var bodyEl = this.getBody();
  22555. DOM.removeClass(bodyEl, 'mce-content-body');
  22556. DOM.removeClass(bodyEl, 'mce-edit-focus');
  22557. DOM.setAttrib(bodyEl, 'contentEditable', null);
  22558. });
  22559. DOM.addClass(targetElm, 'mce-content-body');
  22560. self.contentDocument = doc = settings.content_document || document;
  22561. self.contentWindow = settings.content_window || window;
  22562. self.bodyElement = targetElm;
  22563. // Prevent leak in IE
  22564. settings.content_document = settings.content_window = null;
  22565. // TODO: Fix this
  22566. settings.root_name = targetElm.nodeName.toLowerCase();
  22567. }
  22568. // It will not steal focus while setting contentEditable
  22569. body = self.getBody();
  22570. body.disabled = true;
  22571. if (!settings.readonly) {
  22572. if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
  22573. body.style.position = 'relative';
  22574. }
  22575. body.contentEditable = self.getParam('content_editable_state', true);
  22576. }
  22577. body.disabled = false;
  22578. /**
  22579. * Schema instance, enables you to validate elements and it's children.
  22580. *
  22581. * @property schema
  22582. * @type tinymce.html.Schema
  22583. */
  22584. self.schema = new Schema(settings);
  22585. /**
  22586. * DOM instance for the editor.
  22587. *
  22588. * @property dom
  22589. * @type tinymce.dom.DOMUtils
  22590. * @example
  22591. * // Adds a class to all paragraphs within the editor
  22592. * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
  22593. */
  22594. self.dom = new DOMUtils(doc, {
  22595. keep_values: true,
  22596. url_converter: self.convertURL,
  22597. url_converter_scope: self,
  22598. hex_colors: settings.force_hex_style_colors,
  22599. class_filter: settings.class_filter,
  22600. update_styles: true,
  22601. root_element: settings.content_editable ? self.id : null,
  22602. collect: settings.content_editable,
  22603. schema: self.schema,
  22604. onSetAttrib: function(e) {
  22605. self.fire('SetAttrib', e);
  22606. }
  22607. });
  22608. /**
  22609. * HTML parser will be used when contents is inserted into the editor.
  22610. *
  22611. * @property parser
  22612. * @type tinymce.html.DomParser
  22613. */
  22614. self.parser = new DomParser(settings, self.schema);
  22615. // Convert src and href into data-mce-src, data-mce-href and data-mce-style
  22616. self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
  22617. var i = nodes.length, node, dom = self.dom, value, internalName;
  22618. while (i--) {
  22619. node = nodes[i];
  22620. value = node.attr(name);
  22621. internalName = 'data-mce-' + name;
  22622. // Add internal attribute if we need to we don't on a refresh of the document
  22623. if (!node.attributes.map[internalName]) {
  22624. if (name === "style") {
  22625. node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
  22626. } else if (name === "tabindex") {
  22627. node.attr(internalName, value);
  22628. node.attr(name, null);
  22629. } else {
  22630. node.attr(internalName, self.convertURL(value, name, node.name));
  22631. }
  22632. }
  22633. }
  22634. });
  22635. // Keep scripts from executing
  22636. self.parser.addNodeFilter('script', function(nodes) {
  22637. var i = nodes.length, node;
  22638. while (i--) {
  22639. node = nodes[i];
  22640. node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
  22641. }
  22642. });
  22643. self.parser.addNodeFilter('#cdata', function(nodes) {
  22644. var i = nodes.length, node;
  22645. while (i--) {
  22646. node = nodes[i];
  22647. node.type = 8;
  22648. node.name = '#comment';
  22649. node.value = '[CDATA[' + node.value + ']]';
  22650. }
  22651. });
  22652. self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
  22653. var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
  22654. while (i--) {
  22655. node = nodes[i];
  22656. if (node.isEmpty(nonEmptyElements)) {
  22657. node.empty().append(new Node('br', 1)).shortEnded = true;
  22658. }
  22659. }
  22660. });
  22661. /**
  22662. * DOM serializer for the editor. Will be used when contents is extracted from the editor.
  22663. *
  22664. * @property serializer
  22665. * @type tinymce.dom.Serializer
  22666. * @example
  22667. * // Serializes the first paragraph in the editor into a string
  22668. * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
  22669. */
  22670. self.serializer = new DomSerializer(settings, self);
  22671. /**
  22672. * Selection instance for the editor.
  22673. *
  22674. * @property selection
  22675. * @type tinymce.dom.Selection
  22676. * @example
  22677. * // Sets some contents to the current selection in the editor
  22678. * tinymce.activeEditor.selection.setContent('Some contents');
  22679. *
  22680. * // Gets the current selection
  22681. * alert(tinymce.activeEditor.selection.getContent());
  22682. *
  22683. * // Selects the first paragraph found
  22684. * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
  22685. */
  22686. self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
  22687. /**
  22688. * Formatter instance.
  22689. *
  22690. * @property formatter
  22691. * @type tinymce.Formatter
  22692. */
  22693. self.formatter = new Formatter(self);
  22694. /**
  22695. * Undo manager instance, responsible for handling undo levels.
  22696. *
  22697. * @property undoManager
  22698. * @type tinymce.UndoManager
  22699. * @example
  22700. * // Undoes the last modification to the editor
  22701. * tinymce.activeEditor.undoManager.undo();
  22702. */
  22703. self.undoManager = new UndoManager(self);
  22704. self.forceBlocks = new ForceBlocks(self);
  22705. self.enterKey = new EnterKey(self);
  22706. self.editorCommands = new EditorCommands(self);
  22707. self.fire('PreInit');
  22708. if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
  22709. doc.body.spellcheck = false; // Gecko
  22710. DOM.setAttrib(body, "spellcheck", "false");
  22711. }
  22712. self.fire('PostRender');
  22713. self.quirks = Quirks(self);
  22714. if (settings.directionality) {
  22715. body.dir = settings.directionality;
  22716. }
  22717. if (settings.nowrap) {
  22718. body.style.whiteSpace = "nowrap";
  22719. }
  22720. if (settings.protect) {
  22721. self.on('BeforeSetContent', function(e) {
  22722. each(settings.protect, function(pattern) {
  22723. e.content = e.content.replace(pattern, function(str) {
  22724. return '<!--mce:protected ' + escape(str) + '-->';
  22725. });
  22726. });
  22727. });
  22728. }
  22729. self.on('SetContent', function() {
  22730. self.addVisual(self.getBody());
  22731. });
  22732. // Remove empty contents
  22733. if (settings.padd_empty_editor) {
  22734. self.on('PostProcess', function(e) {
  22735. e.content = e.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
  22736. });
  22737. }
  22738. self.load({initial: true, format: 'html'});
  22739. self.startContent = self.getContent({format: 'raw'});
  22740. /**
  22741. * Is set to true after the editor instance has been initialized
  22742. *
  22743. * @property initialized
  22744. * @type Boolean
  22745. * @example
  22746. * function isEditorInitialized(editor) {
  22747. * return editor && editor.initialized;
  22748. * }
  22749. */
  22750. self.initialized = true;
  22751. each(self._pendingNativeEvents, function(name) {
  22752. self.dom.bind(getEventTarget(self, name), name, function(e) {
  22753. self.fire(e.type, e);
  22754. });
  22755. });
  22756. self.fire('init');
  22757. self.focus(true);
  22758. self.nodeChanged({initial: true});
  22759. self.execCallback('init_instance_callback', self);
  22760. // Add editor specific CSS styles
  22761. if (self.contentStyles.length > 0) {
  22762. contentCssText = '';
  22763. each(self.contentStyles, function(style) {
  22764. contentCssText += style + "\r\n";
  22765. });
  22766. self.dom.addStyle(contentCssText);
  22767. }
  22768. // Load specified content CSS last
  22769. each(self.contentCSS, function(cssUrl) {
  22770. if (!self.loadedCSS[cssUrl]) {
  22771. self.dom.loadCSS(cssUrl);
  22772. self.loadedCSS[cssUrl] = true;
  22773. }
  22774. });
  22775. // Handle auto focus
  22776. if (settings.auto_focus) {
  22777. setTimeout(function () {
  22778. var ed = self.editorManager.get(settings.auto_focus);
  22779. ed.selection.select(ed.getBody(), 1);
  22780. ed.selection.collapse(1);
  22781. ed.getBody().focus();
  22782. ed.getWin().focus();
  22783. }, 100);
  22784. }
  22785. // Clean up references for IE
  22786. targetElm = doc = body = null;
  22787. },
  22788. /**
  22789. * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
  22790. * it will also place DOM focus inside the editor.
  22791. *
  22792. * @method focus
  22793. * @param {Boolean} skip_focus Skip DOM focus. Just set is as the active editor.
  22794. */
  22795. focus: function(skip_focus) {
  22796. var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
  22797. var controlElm, doc = self.getDoc(), body;
  22798. if (!skip_focus) {
  22799. // Get selected control element
  22800. rng = selection.getRng();
  22801. if (rng.item) {
  22802. controlElm = rng.item(0);
  22803. }
  22804. self._refreshContentEditable();
  22805. // Focus the window iframe
  22806. if (!contentEditable) {
  22807. // WebKit needs this call to fire focusin event properly see #5948
  22808. // But Opera pre Blink engine will produce an empty selection so skip Opera
  22809. if (!Env.opera) {
  22810. self.getBody().focus();
  22811. }
  22812. self.getWin().focus();
  22813. }
  22814. // Focus the body as well since it's contentEditable
  22815. if (isGecko || contentEditable) {
  22816. body = self.getBody();
  22817. // Check for setActive since it doesn't scroll to the element
  22818. if (body.setActive) {
  22819. // IE 11 sometimes throws "Invalid function" then fallback to focus
  22820. try {
  22821. body.setActive();
  22822. } catch (ex) {
  22823. body.focus();
  22824. }
  22825. } else {
  22826. body.focus();
  22827. }
  22828. if (contentEditable) {
  22829. selection.normalize();
  22830. }
  22831. }
  22832. // Restore selected control element
  22833. // This is needed when for example an image is selected within a
  22834. // layer a call to focus will then remove the control selection
  22835. if (controlElm && controlElm.ownerDocument == doc) {
  22836. rng = doc.body.createControlRange();
  22837. rng.addElement(controlElm);
  22838. rng.select();
  22839. }
  22840. }
  22841. if (self.editorManager.activeEditor != self) {
  22842. if ((oed = self.editorManager.activeEditor)) {
  22843. oed.fire('deactivate', {relatedTarget: self});
  22844. }
  22845. self.fire('activate', {relatedTarget: oed});
  22846. }
  22847. self.editorManager.activeEditor = self;
  22848. },
  22849. /**
  22850. * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
  22851. * There new event model is a better way to add callback so this method might be removed in the future.
  22852. *
  22853. * @method execCallback
  22854. * @param {String} name Name of the callback to execute.
  22855. * @return {Object} Return value passed from callback function.
  22856. */
  22857. execCallback: function(name) {
  22858. var self = this, callback = self.settings[name], scope;
  22859. if (!callback) {
  22860. return;
  22861. }
  22862. // Look through lookup
  22863. if (self.callbackLookup && (scope = self.callbackLookup[name])) {
  22864. callback = scope.func;
  22865. scope = scope.scope;
  22866. }
  22867. if (typeof(callback) === 'string') {
  22868. scope = callback.replace(/\.\w+$/, '');
  22869. scope = scope ? resolve(scope) : 0;
  22870. callback = resolve(callback);
  22871. self.callbackLookup = self.callbackLookup || {};
  22872. self.callbackLookup[name] = {func: callback, scope: scope};
  22873. }
  22874. return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
  22875. },
  22876. /**
  22877. * Translates the specified string by replacing variables with language pack items it will also check if there is
  22878. * a key mathcin the input.
  22879. *
  22880. * @method translate
  22881. * @param {String} text String to translate by the language pack data.
  22882. * @return {String} Translated string.
  22883. */
  22884. translate: function(text) {
  22885. var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
  22886. if (!text) {
  22887. return '';
  22888. }
  22889. return i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
  22890. return i18n.data[lang + '.' + b] || '{#' + b + '}';
  22891. });
  22892. },
  22893. /**
  22894. * Returns a language pack item by name/key.
  22895. *
  22896. * @method getLang
  22897. * @param {String} name Name/key to get from the language pack.
  22898. * @param {String} defaultVal Optional default value to retrive.
  22899. */
  22900. getLang: function(name, defaultVal) {
  22901. return (
  22902. this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
  22903. (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
  22904. );
  22905. },
  22906. /**
  22907. * Returns a configuration parameter by name.
  22908. *
  22909. * @method getParam
  22910. * @param {String} name Configruation parameter to retrive.
  22911. * @param {String} defaultVal Optional default value to return.
  22912. * @param {String} type Optional type parameter.
  22913. * @return {String} Configuration parameter value or default value.
  22914. * @example
  22915. * // Returns a specific config value from the currently active editor
  22916. * var someval = tinymce.activeEditor.getParam('myvalue');
  22917. *
  22918. * // Returns a specific config value from a specific editor instance by id
  22919. * var someval2 = tinymce.get('my_editor').getParam('myvalue');
  22920. */
  22921. getParam: function(name, defaultVal, type) {
  22922. var value = name in this.settings ? this.settings[name] : defaultVal, output;
  22923. if (type === 'hash') {
  22924. output = {};
  22925. if (typeof(value) === 'string') {
  22926. each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
  22927. value = value.split('=');
  22928. if (value.length > 1) {
  22929. output[trim(value[0])] = trim(value[1]);
  22930. } else {
  22931. output[trim(value[0])] = trim(value);
  22932. }
  22933. });
  22934. } else {
  22935. output = value;
  22936. }
  22937. return output;
  22938. }
  22939. return value;
  22940. },
  22941. /**
  22942. * Distpaches out a onNodeChange event to all observers. This method should be called when you
  22943. * need to update the UI states or element path etc.
  22944. *
  22945. * @method nodeChanged
  22946. */
  22947. nodeChanged: function() {
  22948. var self = this, selection = self.selection, node, parents, root;
  22949. // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
  22950. if (self.initialized && !self.settings.disable_nodechange && !self.settings.readonly) {
  22951. // Get start node
  22952. root = self.getBody();
  22953. node = selection.getStart() || root;
  22954. node = ie && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
  22955. // Edge case for <p>|<img></p>
  22956. if (node.nodeName == 'IMG' && selection.isCollapsed()) {
  22957. node = node.parentNode;
  22958. }
  22959. // Get parents and add them to object
  22960. parents = [];
  22961. self.dom.getParent(node, function(node) {
  22962. if (node === root) {
  22963. return true;
  22964. }
  22965. parents.push(node);
  22966. });
  22967. self.fire('NodeChange', {element: node, parents: parents});
  22968. }
  22969. },
  22970. /**
  22971. * Adds a button that later gets created by the theme in the editors toolbars.
  22972. *
  22973. * @method addButton
  22974. * @param {String} name Button name to add.
  22975. * @param {Object} settings Settings object with title, cmd etc.
  22976. * @example
  22977. * // Adds a custom button to the editor that inserts contents when clicked
  22978. * tinymce.init({
  22979. * ...
  22980. *
  22981. * toolbar: 'example'
  22982. *
  22983. * setup: function(ed) {
  22984. * ed.addButton('example', {
  22985. * title: 'My title',
  22986. * image: '../js/tinymce/plugins/example/img/example.gif',
  22987. * onclick: function() {
  22988. * ed.insertContent('Hello world!!');
  22989. * }
  22990. * });
  22991. * }
  22992. * });
  22993. */
  22994. addButton: function(name, settings) {
  22995. var self = this;
  22996. if (settings.cmd) {
  22997. settings.onclick = function() {
  22998. self.execCommand(settings.cmd);
  22999. };
  23000. }
  23001. if (!settings.text && !settings.icon) {
  23002. settings.icon = name;
  23003. }
  23004. self.buttons = self.buttons || {};
  23005. settings.tooltip = settings.tooltip || settings.title;
  23006. self.buttons[name] = settings;
  23007. },
  23008. /**
  23009. * Adds a menu item to be used in the menus of the theme. There might be multiple instances
  23010. * of this menu item for example it might be used in the main menus of the theme but also in
  23011. * the context menu so make sure that it's self contained and supports multiple instances.
  23012. *
  23013. * @method addMenuItem
  23014. * @param {String} name Menu item name to add.
  23015. * @param {Object} settings Settings object with title, cmd etc.
  23016. * @example
  23017. * // Adds a custom menu item to the editor that inserts contents when clicked
  23018. * // The context option allows you to add the menu item to an existing default menu
  23019. * tinymce.init({
  23020. * ...
  23021. *
  23022. * setup: function(ed) {
  23023. * ed.addMenuItem('example', {
  23024. * text: 'My menu item',
  23025. * context: 'tools',
  23026. * onclick: function() {
  23027. * ed.insertContent('Hello world!!');
  23028. * }
  23029. * });
  23030. * }
  23031. * });
  23032. */
  23033. addMenuItem: function(name, settings) {
  23034. var self = this;
  23035. if (settings.cmd) {
  23036. settings.onclick = function() {
  23037. self.execCommand(settings.cmd);
  23038. };
  23039. }
  23040. self.menuItems = self.menuItems || {};
  23041. self.menuItems[name] = settings;
  23042. },
  23043. /**
  23044. * Adds a custom command to the editor, you can also override existing commands with this method.
  23045. * The command that you add can be executed with execCommand.
  23046. *
  23047. * @method addCommand
  23048. * @param {String} name Command name to add/override.
  23049. * @param {addCommandCallback} callback Function to execute when the command occurs.
  23050. * @param {Object} scope Optional scope to execute the function in.
  23051. * @example
  23052. * // Adds a custom command that later can be executed using execCommand
  23053. * tinymce.init({
  23054. * ...
  23055. *
  23056. * setup: function(ed) {
  23057. * // Register example command
  23058. * ed.addCommand('mycommand', function(ui, v) {
  23059. * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
  23060. * });
  23061. * }
  23062. * });
  23063. */
  23064. addCommand: function(name, callback, scope) {
  23065. /**
  23066. * Callback function that gets called when a command is executed.
  23067. *
  23068. * @callback addCommandCallback
  23069. * @param {Boolean} ui Display UI state true/false.
  23070. * @param {Object} value Optional value for command.
  23071. * @return {Boolean} True/false state if the command was handled or not.
  23072. */
  23073. this.execCommands[name] = {func: callback, scope: scope || this};
  23074. },
  23075. /**
  23076. * Adds a custom query state command to the editor, you can also override existing commands with this method.
  23077. * The command that you add can be executed with queryCommandState function.
  23078. *
  23079. * @method addQueryStateHandler
  23080. * @param {String} name Command name to add/override.
  23081. * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrival occurs.
  23082. * @param {Object} scope Optional scope to execute the function in.
  23083. */
  23084. addQueryStateHandler: function(name, callback, scope) {
  23085. /**
  23086. * Callback function that gets called when a queryCommandState is executed.
  23087. *
  23088. * @callback addQueryStateHandlerCallback
  23089. * @return {Boolean} True/false state if the command is enabled or not like is it bold.
  23090. */
  23091. this.queryStateCommands[name] = {func: callback, scope: scope || this};
  23092. },
  23093. /**
  23094. * Adds a custom query value command to the editor, you can also override existing commands with this method.
  23095. * The command that you add can be executed with queryCommandValue function.
  23096. *
  23097. * @method addQueryValueHandler
  23098. * @param {String} name Command name to add/override.
  23099. * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrival occurs.
  23100. * @param {Object} scope Optional scope to execute the function in.
  23101. */
  23102. addQueryValueHandler: function(name, callback, scope) {
  23103. /**
  23104. * Callback function that gets called when a queryCommandValue is executed.
  23105. *
  23106. * @callback addQueryValueHandlerCallback
  23107. * @return {Object} Value of the command or undefined.
  23108. */
  23109. this.queryValueCommands[name] = {func: callback, scope: scope || this};
  23110. },
  23111. /**
  23112. * Adds a keyboard shortcut for some command or function.
  23113. *
  23114. * @method addShortcut
  23115. * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
  23116. * @param {String} desc Text description for the command.
  23117. * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
  23118. * @param {Object} sc Optional scope to execute the function in.
  23119. * @return {Boolean} true/false state if the shortcut was added or not.
  23120. */
  23121. addShortcut: function(pattern, desc, cmdFunc, scope) {
  23122. this.shortcuts.add(pattern, desc, cmdFunc, scope);
  23123. },
  23124. /**
  23125. * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
  23126. * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
  23127. * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
  23128. * return true it will handle the command as a internal browser command.
  23129. *
  23130. * @method execCommand
  23131. * @param {String} cmd Command name to execute, for example mceLink or Bold.
  23132. * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
  23133. * @param {mixed} value Optional command value, this can be anything.
  23134. * @param {Object} a Optional arguments object.
  23135. */
  23136. execCommand: function(cmd, ui, value, args) {
  23137. var self = this, state = 0, cmdItem;
  23138. if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(cmd) && (!args || !args.skip_focus)) {
  23139. self.focus();
  23140. }
  23141. args = extend({}, args);
  23142. args = self.fire('BeforeExecCommand', {command: cmd, ui: ui, value: value});
  23143. if (args.isDefaultPrevented()) {
  23144. return false;
  23145. }
  23146. // Registred commands
  23147. if ((cmdItem = self.execCommands[cmd])) {
  23148. // Fall through on true
  23149. if (cmdItem.func.call(cmdItem.scope, ui, value) !== true) {
  23150. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23151. return true;
  23152. }
  23153. }
  23154. // Plugin commands
  23155. each(self.plugins, function(p) {
  23156. if (p.execCommand && p.execCommand(cmd, ui, value)) {
  23157. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23158. state = true;
  23159. return false;
  23160. }
  23161. });
  23162. if (state) {
  23163. return state;
  23164. }
  23165. // Theme commands
  23166. if (self.theme && self.theme.execCommand && self.theme.execCommand(cmd, ui, value)) {
  23167. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23168. return true;
  23169. }
  23170. // Editor commands
  23171. if (self.editorCommands.execCommand(cmd, ui, value)) {
  23172. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23173. return true;
  23174. }
  23175. // Browser commands
  23176. self.getDoc().execCommand(cmd, ui, value);
  23177. self.fire('ExecCommand', {command: cmd, ui: ui, value: value});
  23178. },
  23179. /**
  23180. * Returns a command specific state, for example if bold is enabled or not.
  23181. *
  23182. * @method queryCommandState
  23183. * @param {string} cmd Command to query state from.
  23184. * @return {Boolean} Command specific state, for example if bold is enabled or not.
  23185. */
  23186. queryCommandState: function(cmd) {
  23187. var self = this, queryItem, returnVal;
  23188. // Is hidden then return undefined
  23189. if (self._isHidden()) {
  23190. return;
  23191. }
  23192. // Registred commands
  23193. if ((queryItem = self.queryStateCommands[cmd])) {
  23194. returnVal = queryItem.func.call(queryItem.scope);
  23195. // Fall though on true
  23196. if (returnVal !== true) {
  23197. return returnVal;
  23198. }
  23199. }
  23200. // Editor commands
  23201. returnVal = self.editorCommands.queryCommandState(cmd);
  23202. if (returnVal !== -1) {
  23203. return returnVal;
  23204. }
  23205. // Browser commands
  23206. try {
  23207. return self.getDoc().queryCommandState(cmd);
  23208. } catch (ex) {
  23209. // Fails sometimes see bug: 1896577
  23210. }
  23211. },
  23212. /**
  23213. * Returns a command specific value, for example the current font size.
  23214. *
  23215. * @method queryCommandValue
  23216. * @param {string} cmd Command to query value from.
  23217. * @return {Object} Command specific value, for example the current font size.
  23218. */
  23219. queryCommandValue: function(cmd) {
  23220. var self = this, queryItem, returnVal;
  23221. // Is hidden then return undefined
  23222. if (self._isHidden()) {
  23223. return;
  23224. }
  23225. // Registred commands
  23226. if ((queryItem = self.queryValueCommands[cmd])) {
  23227. returnVal = queryItem.func.call(queryItem.scope);
  23228. // Fall though on true
  23229. if (returnVal !== true) {
  23230. return returnVal;
  23231. }
  23232. }
  23233. // Editor commands
  23234. returnVal = self.editorCommands.queryCommandValue(cmd);
  23235. if (returnVal !== undefined) {
  23236. return returnVal;
  23237. }
  23238. // Browser commands
  23239. try {
  23240. return self.getDoc().queryCommandValue(cmd);
  23241. } catch (ex) {
  23242. // Fails sometimes see bug: 1896577
  23243. }
  23244. },
  23245. /**
  23246. * Shows the editor and hides any textarea/div that the editor is supposed to replace.
  23247. *
  23248. * @method show
  23249. */
  23250. show: function() {
  23251. var self = this;
  23252. DOM.show(self.getContainer());
  23253. DOM.hide(self.id);
  23254. self.load();
  23255. self.fire('show');
  23256. },
  23257. /**
  23258. * Hides the editor and shows any textarea/div that the editor is supposed to replace.
  23259. *
  23260. * @method hide
  23261. */
  23262. hide: function() {
  23263. var self = this, doc = self.getDoc();
  23264. // Fixed bug where IE has a blinking cursor left from the editor
  23265. if (ie && doc && !self.inline) {
  23266. doc.execCommand('SelectAll');
  23267. }
  23268. // We must save before we hide so Safari doesn't crash
  23269. self.save();
  23270. // defer the call to hide to prevent an IE9 crash #4921
  23271. DOM.hide(self.getContainer());
  23272. DOM.setStyle(self.id, 'display', self.orgDisplay);
  23273. self.fire('hide');
  23274. },
  23275. /**
  23276. * Returns true/false if the editor is hidden or not.
  23277. *
  23278. * @method isHidden
  23279. * @return {Boolean} True/false if the editor is hidden or not.
  23280. */
  23281. isHidden: function() {
  23282. return !DOM.isHidden(this.id);
  23283. },
  23284. /**
  23285. * Sets the progress state, this will display a throbber/progess for the editor.
  23286. * This is ideal for asycronous operations like an AJAX save call.
  23287. *
  23288. * @method setProgressState
  23289. * @param {Boolean} state Boolean state if the progress should be shown or hidden.
  23290. * @param {Number} time Optional time to wait before the progress gets shown.
  23291. * @return {Boolean} Same as the input state.
  23292. * @example
  23293. * // Show progress for the active editor
  23294. * tinymce.activeEditor.setProgressState(true);
  23295. *
  23296. * // Hide progress for the active editor
  23297. * tinymce.activeEditor.setProgressState(false);
  23298. *
  23299. * // Show progress after 3 seconds
  23300. * tinymce.activeEditor.setProgressState(true, 3000);
  23301. */
  23302. setProgressState: function(state, time) {
  23303. this.fire('ProgressState', {state: state, time: time});
  23304. },
  23305. /**
  23306. * Loads contents from the textarea or div element that got converted into an editor instance.
  23307. * This method will move the contents from that textarea or div into the editor by using setContent
  23308. * so all events etc that method has will get dispatched as well.
  23309. *
  23310. * @method load
  23311. * @param {Object} args Optional content object, this gets passed around through the whole load process.
  23312. * @return {String} HTML string that got set into the editor.
  23313. */
  23314. load: function(args) {
  23315. var self = this, elm = self.getElement(), html;
  23316. if (elm) {
  23317. args = args || {};
  23318. args.load = true;
  23319. html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
  23320. args.element = elm;
  23321. if (!args.no_events) {
  23322. self.fire('LoadContent', args);
  23323. }
  23324. args.element = elm = null;
  23325. return html;
  23326. }
  23327. },
  23328. /**
  23329. * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
  23330. * This method will move the HTML contents from the editor into that textarea or div by getContent
  23331. * so all events etc that method has will get dispatched as well.
  23332. *
  23333. * @method save
  23334. * @param {Object} args Optional content object, this gets passed around through the whole save process.
  23335. * @return {String} HTML string that got set into the textarea/div.
  23336. */
  23337. save: function(args) {
  23338. var self = this, elm = self.getElement(), html, form;
  23339. if (!elm || !self.initialized) {
  23340. return;
  23341. }
  23342. args = args || {};
  23343. args.save = true;
  23344. args.element = elm;
  23345. html = args.content = self.getContent(args);
  23346. if (!args.no_events) {
  23347. self.fire('SaveContent', args);
  23348. }
  23349. html = args.content;
  23350. if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
  23351. // Update DIV element when not in inline mode
  23352. if (!self.inline) {
  23353. elm.innerHTML = html;
  23354. }
  23355. // Update hidden form element
  23356. if ((form = DOM.getParent(self.id, 'form'))) {
  23357. each(form.elements, function(elm) {
  23358. if (elm.name == self.id) {
  23359. elm.value = html;
  23360. return false;
  23361. }
  23362. });
  23363. }
  23364. } else {
  23365. elm.value = html;
  23366. }
  23367. args.element = elm = null;
  23368. if (args.set_dirty !== false) {
  23369. self.isNotDirty = true;
  23370. }
  23371. return html;
  23372. },
  23373. /**
  23374. * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
  23375. * the different cleanup rules options.
  23376. *
  23377. * @method setContent
  23378. * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
  23379. * @param {Object} args Optional content object, this gets passed around through the whole set process.
  23380. * @return {String} HTML string that got set into the editor.
  23381. * @example
  23382. * // Sets the HTML contents of the activeEditor editor
  23383. * tinymce.activeEditor.setContent('<span>some</span> html');
  23384. *
  23385. * // Sets the raw contents of the activeEditor editor
  23386. * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
  23387. *
  23388. * // Sets the content of a specific editor (my_editor in this example)
  23389. * tinymce.get('my_editor').setContent(data);
  23390. *
  23391. * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
  23392. * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
  23393. */
  23394. setContent: function(content, args) {
  23395. var self = this, body = self.getBody(), forcedRootBlockName;
  23396. // Setup args object
  23397. args = args || {};
  23398. args.format = args.format || 'html';
  23399. args.set = true;
  23400. args.content = content;
  23401. // Do preprocessing
  23402. if (!args.no_events) {
  23403. self.fire('BeforeSetContent', args);
  23404. }
  23405. content = args.content;
  23406. // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
  23407. // It will also be impossible to place the caret in the editor unless there is a BR element present
  23408. if (content.length === 0 || /^\s+$/.test(content)) {
  23409. forcedRootBlockName = self.settings.forced_root_block;
  23410. // Check if forcedRootBlock is configured and that the block is a valid child of the body
  23411. if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
  23412. // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
  23413. content = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
  23414. content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
  23415. } else if (!ie) {
  23416. // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
  23417. content = '<br data-mce-bogus="1">';
  23418. }
  23419. body.innerHTML = content;
  23420. self.fire('SetContent', args);
  23421. } else {
  23422. // Parse and serialize the html
  23423. if (args.format !== 'raw') {
  23424. content = new Serializer({}, self.schema).serialize(
  23425. self.parser.parse(content, {isRootContent: true})
  23426. );
  23427. }
  23428. // Set the new cleaned contents to the editor
  23429. args.content = trim(content);
  23430. self.dom.setHTML(body, args.content);
  23431. // Do post processing
  23432. if (!args.no_events) {
  23433. self.fire('SetContent', args);
  23434. }
  23435. // Don't normalize selection if the focused element isn't the body in
  23436. // content editable mode since it will steal focus otherwise
  23437. /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
  23438. self.selection.normalize();
  23439. }*/
  23440. }
  23441. return args.content;
  23442. },
  23443. /**
  23444. * Gets the content from the editor instance, this will cleanup the content before it gets returned using
  23445. * the different cleanup rules options.
  23446. *
  23447. * @method getContent
  23448. * @param {Object} args Optional content object, this gets passed around through the whole get process.
  23449. * @return {String} Cleaned content string, normally HTML contents.
  23450. * @example
  23451. * // Get the HTML contents of the currently active editor
  23452. * console.debug(tinymce.activeEditor.getContent());
  23453. *
  23454. * // Get the raw contents of the currently active editor
  23455. * tinymce.activeEditor.getContent({format: 'raw'});
  23456. *
  23457. * // Get content of a specific editor:
  23458. * tinymce.get('content id').getContent()
  23459. */
  23460. getContent: function(args) {
  23461. var self = this, content, body = self.getBody();
  23462. // Setup args object
  23463. args = args || {};
  23464. args.format = args.format || 'html';
  23465. args.get = true;
  23466. args.getInner = true;
  23467. // Do preprocessing
  23468. if (!args.no_events) {
  23469. self.fire('BeforeGetContent', args);
  23470. }
  23471. // Get raw contents or by default the cleaned contents
  23472. if (args.format == 'raw') {
  23473. content = body.innerHTML;
  23474. } else if (args.format == 'text') {
  23475. content = body.innerText || body.textContent;
  23476. } else {
  23477. content = self.serializer.serialize(body, args);
  23478. }
  23479. // Trim whitespace in beginning/end of HTML
  23480. if (args.format != 'text') {
  23481. args.content = trim(content);
  23482. } else {
  23483. args.content = content;
  23484. }
  23485. // Do post processing
  23486. if (!args.no_events) {
  23487. self.fire('GetContent', args);
  23488. }
  23489. return args.content;
  23490. },
  23491. /**
  23492. * Inserts content at caret position.
  23493. *
  23494. * @method insertContent
  23495. * @param {String} content Content to insert.
  23496. */
  23497. insertContent: function(content) {
  23498. this.execCommand('mceInsertContent', false, content);
  23499. },
  23500. /**
  23501. * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  23502. *
  23503. * @method isDirty
  23504. * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
  23505. * @example
  23506. * if (tinymce.activeEditor.isDirty())
  23507. * alert("You must save your contents.");
  23508. */
  23509. isDirty: function() {
  23510. return !this.isNotDirty;
  23511. },
  23512. /**
  23513. * Returns the editors container element. The container element wrappes in
  23514. * all the elements added to the page for the editor. Such as UI, iframe etc.
  23515. *
  23516. * @method getContainer
  23517. * @return {Element} HTML DOM element for the editor container.
  23518. */
  23519. getContainer: function() {
  23520. var self = this;
  23521. if (!self.container) {
  23522. self.container = DOM.get(self.editorContainer || self.id + '_parent');
  23523. }
  23524. return self.container;
  23525. },
  23526. /**
  23527. * Returns the editors content area container element. The this element is the one who
  23528. * holds the iframe or the editable element.
  23529. *
  23530. * @method getContentAreaContainer
  23531. * @return {Element} HTML DOM element for the editor area container.
  23532. */
  23533. getContentAreaContainer: function() {
  23534. return this.contentAreaContainer;
  23535. },
  23536. /**
  23537. * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
  23538. *
  23539. * @method getElement
  23540. * @return {Element} HTML DOM element for the replaced element.
  23541. */
  23542. getElement: function() {
  23543. return DOM.get(this.settings.content_element || this.id);
  23544. },
  23545. /**
  23546. * Returns the iframes window object.
  23547. *
  23548. * @method getWin
  23549. * @return {Window} Iframe DOM window object.
  23550. */
  23551. getWin: function() {
  23552. var self = this, elm;
  23553. if (!self.contentWindow) {
  23554. elm = DOM.get(self.id + "_ifr");
  23555. if (elm) {
  23556. self.contentWindow = elm.contentWindow;
  23557. }
  23558. }
  23559. return self.contentWindow;
  23560. },
  23561. /**
  23562. * Returns the iframes document object.
  23563. *
  23564. * @method getDoc
  23565. * @return {Document} Iframe DOM document object.
  23566. */
  23567. getDoc: function() {
  23568. var self = this, win;
  23569. if (!self.contentDocument) {
  23570. win = self.getWin();
  23571. if (win) {
  23572. self.contentDocument = win.document;
  23573. }
  23574. }
  23575. return self.contentDocument;
  23576. },
  23577. /**
  23578. * Returns the iframes body element.
  23579. *
  23580. * @method getBody
  23581. * @return {Element} Iframe body element.
  23582. */
  23583. getBody: function() {
  23584. return this.bodyElement || this.getDoc().body;
  23585. },
  23586. /**
  23587. * URL converter function this gets executed each time a user adds an img, a or
  23588. * any other element that has a URL in it. This will be called both by the DOM and HTML
  23589. * manipulation functions.
  23590. *
  23591. * @method convertURL
  23592. * @param {string} url URL to convert.
  23593. * @param {string} name Attribute name src, href etc.
  23594. * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
  23595. * @return {string} Converted URL string.
  23596. */
  23597. convertURL: function(url, name, elm) {
  23598. var self = this, settings = self.settings;
  23599. // Use callback instead
  23600. if (settings.urlconverter_callback) {
  23601. return self.execCallback('urlconverter_callback', url, elm, true, name);
  23602. }
  23603. // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
  23604. if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
  23605. return url;
  23606. }
  23607. // Convert to relative
  23608. if (settings.relative_urls) {
  23609. return self.documentBaseURI.toRelative(url);
  23610. }
  23611. // Convert to absolute
  23612. url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
  23613. return url;
  23614. },
  23615. /**
  23616. * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
  23617. *
  23618. * @method addVisual
  23619. * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
  23620. */
  23621. addVisual: function(elm) {
  23622. var self = this, settings = self.settings, dom = self.dom, cls;
  23623. elm = elm || self.getBody();
  23624. if (self.hasVisual === undefined) {
  23625. self.hasVisual = settings.visual;
  23626. }
  23627. each(dom.select('table,a', elm), function(elm) {
  23628. var value;
  23629. switch (elm.nodeName) {
  23630. case 'TABLE':
  23631. cls = settings.visual_table_class || 'mce-item-table';
  23632. value = dom.getAttrib(elm, 'border');
  23633. if (!value || value == '0') {
  23634. if (self.hasVisual) {
  23635. dom.addClass(elm, cls);
  23636. } else {
  23637. dom.removeClass(elm, cls);
  23638. }
  23639. }
  23640. return;
  23641. case 'A':
  23642. if (!dom.getAttrib(elm, 'href', false)) {
  23643. value = dom.getAttrib(elm, 'name') || elm.id;
  23644. cls = settings.visual_anchor_class || 'mce-item-anchor';
  23645. if (value) {
  23646. if (self.hasVisual) {
  23647. dom.addClass(elm, cls);
  23648. } else {
  23649. dom.removeClass(elm, cls);
  23650. }
  23651. }
  23652. }
  23653. return;
  23654. }
  23655. });
  23656. self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
  23657. },
  23658. /**
  23659. * Removes the editor from the dom and tinymce collection.
  23660. *
  23661. * @method remove
  23662. */
  23663. remove: function() {
  23664. var self = this;
  23665. if (!self.removed) {
  23666. self.save();
  23667. self.fire('remove');
  23668. self.off();
  23669. self.removed = 1; // Cancels post remove event execution
  23670. // Remove any hidden input
  23671. if (self.hasHiddenInput) {
  23672. DOM.remove(self.getElement().nextSibling);
  23673. }
  23674. DOM.setStyle(self.id, 'display', self.orgDisplay);
  23675. // Don't clear the window or document if content editable
  23676. // is enabled since other instances might still be present
  23677. if (!self.settings.content_editable) {
  23678. Event.unbind(self.getWin());
  23679. Event.unbind(self.getDoc());
  23680. }
  23681. var elm = self.getContainer();
  23682. Event.unbind(self.getBody());
  23683. Event.unbind(elm);
  23684. self.editorManager.remove(self);
  23685. DOM.remove(elm);
  23686. self.destroy();
  23687. }
  23688. },
  23689. bindNative: function(name) {
  23690. var self = this;
  23691. if (self.settings.readonly) {
  23692. return;
  23693. }
  23694. if (self.initialized) {
  23695. self.dom.bind(getEventTarget(self, name), name, function(e) {
  23696. self.fire(name, e);
  23697. });
  23698. } else {
  23699. if (!self._pendingNativeEvents) {
  23700. self._pendingNativeEvents = [name];
  23701. } else {
  23702. self._pendingNativeEvents.push(name);
  23703. }
  23704. }
  23705. },
  23706. unbindNative: function(name) {
  23707. var self = this;
  23708. if (self.initialized) {
  23709. self.dom.unbind(name);
  23710. }
  23711. },
  23712. /**
  23713. * Destroys the editor instance by removing all events, element references or other resources
  23714. * that could leak memory. This method will be called automatically when the page is unloaded
  23715. * but you can also call it directly if you know what you are doing.
  23716. *
  23717. * @method destroy
  23718. * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
  23719. */
  23720. destroy: function(automatic) {
  23721. var self = this, form;
  23722. // One time is enough
  23723. if (self.destroyed) {
  23724. return;
  23725. }
  23726. // If user manually calls destroy and not remove
  23727. // Users seems to have logic that calls destroy instead of remove
  23728. if (!automatic && !self.removed) {
  23729. self.remove();
  23730. return;
  23731. }
  23732. // We must unbind on Gecko since it would otherwise produce the pesky "attempt
  23733. // to run compile-and-go script on a cleared scope" message
  23734. if (automatic && isGecko) {
  23735. Event.unbind(self.getDoc());
  23736. Event.unbind(self.getWin());
  23737. Event.unbind(self.getBody());
  23738. }
  23739. if (!automatic) {
  23740. self.editorManager.off('beforeunload', self._beforeUnload);
  23741. // Manual destroy
  23742. if (self.theme && self.theme.destroy) {
  23743. self.theme.destroy();
  23744. }
  23745. // Destroy controls, selection and dom
  23746. self.selection.destroy();
  23747. self.dom.destroy();
  23748. }
  23749. form = self.formElement;
  23750. if (form) {
  23751. if (form._mceOldSubmit) {
  23752. form.submit = form._mceOldSubmit;
  23753. form._mceOldSubmit = null;
  23754. }
  23755. DOM.unbind(form, 'submit reset', self.formEventDelegate);
  23756. }
  23757. self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
  23758. self.settings.content_element = self.bodyElement = self.contentDocument = self.contentWindow = null;
  23759. if (self.selection) {
  23760. self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
  23761. }
  23762. self.destroyed = 1;
  23763. },
  23764. // Internal functions
  23765. _refreshContentEditable: function() {
  23766. var self = this, body, parent;
  23767. // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
  23768. if (self._isHidden()) {
  23769. body = self.getBody();
  23770. parent = body.parentNode;
  23771. parent.removeChild(body);
  23772. parent.appendChild(body);
  23773. body.focus();
  23774. }
  23775. },
  23776. _isHidden: function() {
  23777. var sel;
  23778. if (!isGecko) {
  23779. return 0;
  23780. }
  23781. // Weird, wheres that cursor selection?
  23782. sel = this.selection.getSel();
  23783. return (!sel || !sel.rangeCount || sel.rangeCount === 0);
  23784. }
  23785. };
  23786. extend(Editor.prototype, Observable);
  23787. return Editor;
  23788. });
  23789. // Included from: js/tinymce/classes/util/I18n.js
  23790. /**
  23791. * I18n.js
  23792. *
  23793. * Copyright, Moxiecode Systems AB
  23794. * Released under LGPL License.
  23795. *
  23796. * License: http://www.tinymce.com/license
  23797. * Contributing: http://www.tinymce.com/contributing
  23798. */
  23799. /**
  23800. * I18n class that handles translation of TinyMCE UI.
  23801. * Uses po style with csharp style parameters.
  23802. *
  23803. * @class tinymce.util.I18n
  23804. */
  23805. define("tinymce/util/I18n", [], function() {
  23806. "use strict";
  23807. var data = {};
  23808. return {
  23809. /**
  23810. * Property gets set to true if a RTL language pack was loaded.
  23811. *
  23812. * @property rtl
  23813. * @type Boolean
  23814. */
  23815. rtl: false,
  23816. /**
  23817. * Adds translations for a specific language code.
  23818. *
  23819. * @method add
  23820. * @param {String} code Language code like sv_SE.
  23821. * @param {Array} items Name/value array with English en_US to sv_SE.
  23822. */
  23823. add: function(code, items) {
  23824. for (var name in items) {
  23825. data[name] = items[name];
  23826. }
  23827. this.rtl = this.rtl || data._dir === 'rtl';
  23828. },
  23829. /**
  23830. * Translates the specified text.
  23831. *
  23832. * It has a few formats:
  23833. * I18n.translate("Text");
  23834. * I18n.translate(["Text {0}/{1}", 0, 1]);
  23835. * I18n.translate({raw: "Raw string"});
  23836. *
  23837. * @method translate
  23838. * @param {String/Object/Array} text Text to translate.
  23839. * @return {String} String that got translated.
  23840. */
  23841. translate: function(text) {
  23842. if (typeof(text) == "undefined") {
  23843. return text;
  23844. }
  23845. if (typeof(text) != "string" && text.raw) {
  23846. return text.raw;
  23847. }
  23848. if (text.push) {
  23849. var values = text.slice(1);
  23850. text = (data[text[0]] || text[0]).replace(/\{([^\}]+)\}/g, function(match1, match2) {
  23851. return values[match2];
  23852. });
  23853. }
  23854. return data[text] || text;
  23855. },
  23856. data: data
  23857. };
  23858. });
  23859. // Included from: js/tinymce/classes/FocusManager.js
  23860. /**
  23861. * FocusManager.js
  23862. *
  23863. * Copyright, Moxiecode Systems AB
  23864. * Released under LGPL License.
  23865. *
  23866. * License: http://www.tinymce.com/license
  23867. * Contributing: http://www.tinymce.com/contributing
  23868. */
  23869. /**
  23870. * This class manages the focus/blur state of the editor. This class is needed since some
  23871. * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
  23872. *
  23873. * This class will fire two events focus and blur on the editor instances that got affected.
  23874. * It will also handle the restore of selection when the focus is lost and returned.
  23875. *
  23876. * @class tinymce.FocusManager
  23877. */
  23878. define("tinymce/FocusManager", [
  23879. "tinymce/dom/DOMUtils",
  23880. "tinymce/Env"
  23881. ], function(DOMUtils, Env) {
  23882. var selectionChangeHandler, documentFocusInHandler, DOM = DOMUtils.DOM;
  23883. /**
  23884. * Constructs a new focus manager instance.
  23885. *
  23886. * @constructor FocusManager
  23887. * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
  23888. */
  23889. function FocusManager(editorManager) {
  23890. function getActiveElement() {
  23891. try {
  23892. return document.activeElement;
  23893. } catch (ex) {
  23894. // IE sometimes fails to get the activeElement when resizing table
  23895. // TODO: Investigate this
  23896. return document.body;
  23897. }
  23898. }
  23899. // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
  23900. // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
  23901. function createBookmark(rng) {
  23902. if (rng && rng.startContainer) {
  23903. return {
  23904. startContainer: rng.startContainer,
  23905. startOffset: rng.startOffset,
  23906. endContainer: rng.endContainer,
  23907. endOffset: rng.endOffset
  23908. };
  23909. }
  23910. return rng;
  23911. }
  23912. function bookmarkToRng(editor, bookmark) {
  23913. var rng;
  23914. if (bookmark.startContainer) {
  23915. rng = editor.getDoc().createRange();
  23916. rng.setStart(bookmark.startContainer, bookmark.startOffset);
  23917. rng.setEnd(bookmark.endContainer, bookmark.endOffset);
  23918. } else {
  23919. rng = bookmark;
  23920. }
  23921. return rng;
  23922. }
  23923. function isUIElement(elm) {
  23924. return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
  23925. }
  23926. function isNodeInBodyOfEditor(node, editor) {
  23927. var body = editor.getBody();
  23928. while (node) {
  23929. if (node == body) {
  23930. return true;
  23931. }
  23932. node = node.parentNode;
  23933. }
  23934. }
  23935. function registerEvents(e) {
  23936. var editor = e.editor;
  23937. editor.on('init', function() {
  23938. // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
  23939. if (editor.inline || Env.ie) {
  23940. // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
  23941. editor.on('nodechange keyup', function() {
  23942. var node = document.activeElement;
  23943. // IE 11 reports active element as iframe not body of iframe
  23944. if (node && node.id == editor.id + '_ifr') {
  23945. node = editor.getBody();
  23946. }
  23947. if (isNodeInBodyOfEditor(node, editor)) {
  23948. editor.lastRng = editor.selection.getRng();
  23949. }
  23950. });
  23951. // Handles the issue with WebKit not retaining selection within inline document
  23952. // If the user releases the mouse out side the body since a mouse up event wont occur on the body
  23953. if (Env.webkit && !selectionChangeHandler) {
  23954. selectionChangeHandler = function() {
  23955. var activeEditor = editorManager.activeEditor;
  23956. if (activeEditor && activeEditor.selection) {
  23957. var rng = activeEditor.selection.getRng();
  23958. // Store when it's non collapsed
  23959. if (rng && !rng.collapsed) {
  23960. editor.lastRng = rng;
  23961. }
  23962. }
  23963. };
  23964. DOM.bind(document, 'selectionchange', selectionChangeHandler);
  23965. }
  23966. }
  23967. });
  23968. editor.on('setcontent', function() {
  23969. editor.lastRng = null;
  23970. });
  23971. // Remove last selection bookmark on mousedown see #6305
  23972. editor.on('mousedown', function() {
  23973. editor.selection.lastFocusBookmark = null;
  23974. });
  23975. editor.on('focusin', function() {
  23976. var focusedEditor = editorManager.focusedEditor;
  23977. if (editor.selection.lastFocusBookmark) {
  23978. editor.selection.setRng(bookmarkToRng(editor, editor.selection.lastFocusBookmark));
  23979. editor.selection.lastFocusBookmark = null;
  23980. }
  23981. if (focusedEditor != editor) {
  23982. if (focusedEditor) {
  23983. focusedEditor.fire('blur', {focusedEditor: editor});
  23984. }
  23985. editorManager.activeEditor = editor;
  23986. editorManager.focusedEditor = editor;
  23987. editor.fire('focus', {blurredEditor: focusedEditor});
  23988. editor.focus(true);
  23989. }
  23990. editor.lastRng = null;
  23991. });
  23992. editor.on('focusout', function() {
  23993. window.setTimeout(function() {
  23994. var focusedEditor = editorManager.focusedEditor;
  23995. // Still the same editor the the blur was outside any editor UI
  23996. if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
  23997. editor.fire('blur', {focusedEditor: null});
  23998. editorManager.focusedEditor = null;
  23999. // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
  24000. if (editor.selection) {
  24001. editor.selection.lastFocusBookmark = null;
  24002. }
  24003. }
  24004. }, 0);
  24005. });
  24006. if (!documentFocusInHandler) {
  24007. documentFocusInHandler = function(e) {
  24008. var activeEditor = editorManager.activeEditor;
  24009. if (activeEditor && e.target.ownerDocument == document) {
  24010. // Check to make sure we have a valid selection
  24011. if (activeEditor.selection) {
  24012. activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.lastRng);
  24013. }
  24014. // Fire a blur event if the element isn't a UI element
  24015. if (!isUIElement(e.target) && editorManager.focusedEditor == activeEditor) {
  24016. activeEditor.fire('blur', {focusedEditor: null});
  24017. editorManager.focusedEditor = null;
  24018. }
  24019. }
  24020. };
  24021. // Check if focus is moved to an element outside the active editor by checking if the target node
  24022. // isn't within the body of the activeEditor nor a UI element such as a dialog child control
  24023. DOM.bind(document, 'focusin', documentFocusInHandler);
  24024. }
  24025. }
  24026. function unregisterDocumentEvents(e) {
  24027. if (editorManager.focusedEditor == e.editor) {
  24028. editorManager.focusedEditor = null;
  24029. }
  24030. if (!editorManager.activeEditor) {
  24031. DOM.unbind(document, 'selectionchange', selectionChangeHandler);
  24032. DOM.unbind(document, 'focusin', documentFocusInHandler);
  24033. selectionChangeHandler = documentFocusInHandler = null;
  24034. }
  24035. }
  24036. editorManager.on('AddEditor', registerEvents);
  24037. editorManager.on('RemoveEditor', unregisterDocumentEvents);
  24038. }
  24039. /**
  24040. * Returns true if the specified element is part of the UI for example an button or text input.
  24041. *
  24042. * @method isEditorUIElement
  24043. * @param {Element} elm Element to check if it's part of the UI or not.
  24044. * @return {Boolean} True/false state if the element is part of the UI or not.
  24045. */
  24046. FocusManager.isEditorUIElement = function(elm) {
  24047. // Needs to be converted to string since svg can have focus: #6776
  24048. return elm.className.toString().indexOf('mce-') !== -1;
  24049. };
  24050. return FocusManager;
  24051. });
  24052. // Included from: js/tinymce/classes/EditorManager.js
  24053. /**
  24054. * EditorManager.js
  24055. *
  24056. * Copyright, Moxiecode Systems AB
  24057. * Released under LGPL License.
  24058. *
  24059. * License: http://www.tinymce.com/license
  24060. * Contributing: http://www.tinymce.com/contributing
  24061. */
  24062. /**
  24063. * This class used as a factory for manager for tinymce.Editor instances.
  24064. *
  24065. * @example
  24066. * tinymce.EditorManager.init({});
  24067. *
  24068. * @class tinymce.EditorManager
  24069. * @mixes tinymce.util.Observable
  24070. * @static
  24071. */
  24072. define("tinymce/EditorManager", [
  24073. "tinymce/Editor",
  24074. "tinymce/dom/DOMUtils",
  24075. "tinymce/util/URI",
  24076. "tinymce/Env",
  24077. "tinymce/util/Tools",
  24078. "tinymce/util/Observable",
  24079. "tinymce/util/I18n",
  24080. "tinymce/FocusManager"
  24081. ], function(Editor, DOMUtils, URI, Env, Tools, Observable, I18n, FocusManager) {
  24082. var DOM = DOMUtils.DOM;
  24083. var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
  24084. var instanceCounter = 0, beforeUnloadDelegate;
  24085. var EditorManager = {
  24086. /**
  24087. * Major version of TinyMCE build.
  24088. *
  24089. * @property majorVersion
  24090. * @type String
  24091. */
  24092. majorVersion : '4',
  24093. /**
  24094. * Minor version of TinyMCE build.
  24095. *
  24096. * @property minorVersion
  24097. * @type String
  24098. */
  24099. minorVersion : '0.21',
  24100. /**
  24101. * Release date of TinyMCE build.
  24102. *
  24103. * @property releaseDate
  24104. * @type String
  24105. */
  24106. releaseDate: '2014-03-xx',
  24107. /**
  24108. * Collection of editor instances.
  24109. *
  24110. * @property editors
  24111. * @type Object
  24112. * @example
  24113. * for (edId in tinymce.editors)
  24114. * tinymce.editors[edId].save();
  24115. */
  24116. editors: [],
  24117. /**
  24118. * Collection of language pack data.
  24119. *
  24120. * @property i18n
  24121. * @type Object
  24122. */
  24123. i18n: I18n,
  24124. /**
  24125. * Currently active editor instance.
  24126. *
  24127. * @property activeEditor
  24128. * @type tinymce.Editor
  24129. * @example
  24130. * tinyMCE.activeEditor.selection.getContent();
  24131. * tinymce.EditorManager.activeEditor.selection.getContent();
  24132. */
  24133. activeEditor: null,
  24134. setup: function() {
  24135. var self = this, baseURL, documentBaseURL, suffix = "", preInit;
  24136. // Get base URL for the current document
  24137. documentBaseURL = document.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
  24138. if (!/[\/\\]$/.test(documentBaseURL)) {
  24139. documentBaseURL += '/';
  24140. }
  24141. // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
  24142. preInit = window.tinymce || window.tinyMCEPreInit;
  24143. if (preInit) {
  24144. baseURL = preInit.base || preInit.baseURL;
  24145. suffix = preInit.suffix;
  24146. } else {
  24147. // Get base where the tinymce script is located
  24148. var scripts = document.getElementsByTagName('script');
  24149. for (var i = 0; i < scripts.length; i++) {
  24150. var src = scripts[i].src;
  24151. // Script types supported:
  24152. // tinymce.js tinymce.min.js tinymce.dev.js
  24153. // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
  24154. // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
  24155. if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
  24156. if (src.indexOf('.min') != -1) {
  24157. suffix = '.min';
  24158. }
  24159. baseURL = src.substring(0, src.lastIndexOf('/'));
  24160. break;
  24161. }
  24162. }
  24163. }
  24164. /**
  24165. * Base URL where the root directory if TinyMCE is located.
  24166. *
  24167. * @property baseURL
  24168. * @type String
  24169. */
  24170. self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
  24171. /**
  24172. * Document base URL where the current document is located.
  24173. *
  24174. * @property documentBaseURL
  24175. * @type String
  24176. */
  24177. self.documentBaseURL = documentBaseURL;
  24178. /**
  24179. * Absolute baseURI for the installation path of TinyMCE.
  24180. *
  24181. * @property baseURI
  24182. * @type tinymce.util.URI
  24183. */
  24184. self.baseURI = new URI(self.baseURL);
  24185. /**
  24186. * Current suffix to add to each plugin/theme that gets loaded for example ".min".
  24187. *
  24188. * @property suffix
  24189. * @type String
  24190. */
  24191. self.suffix = suffix;
  24192. self.focusManager = new FocusManager(self);
  24193. },
  24194. /**
  24195. * Initializes a set of editors. This method will create editors based on various settings.
  24196. *
  24197. * @method init
  24198. * @param {Object} settings Settings object to be passed to each editor instance.
  24199. * @example
  24200. * // Initializes a editor using the longer method
  24201. * tinymce.EditorManager.init({
  24202. * some_settings : 'some value'
  24203. * });
  24204. *
  24205. * // Initializes a editor instance using the shorter version
  24206. * tinyMCE.init({
  24207. * some_settings : 'some value'
  24208. * });
  24209. */
  24210. init: function(settings) {
  24211. var self = this, editors = [], editor;
  24212. function createId(elm) {
  24213. var id = elm.id;
  24214. // Use element id, or unique name or generate a unique id
  24215. if (!id) {
  24216. id = elm.name;
  24217. if (id && !DOM.get(id)) {
  24218. id = elm.name;
  24219. } else {
  24220. // Generate unique name
  24221. id = DOM.uniqueId();
  24222. }
  24223. elm.setAttribute('id', id);
  24224. }
  24225. return id;
  24226. }
  24227. function execCallback(se, n, s) {
  24228. var f = se[n];
  24229. if (!f) {
  24230. return;
  24231. }
  24232. return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
  24233. }
  24234. function hasClass(n, c) {
  24235. return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
  24236. }
  24237. function readyHandler() {
  24238. var l, co;
  24239. DOM.unbind(window, 'ready', readyHandler);
  24240. execCallback(settings, 'onpageload');
  24241. if (settings.types) {
  24242. // Process type specific selector
  24243. each(settings.types, function(type) {
  24244. each(DOM.select(type.selector), function(elm) {
  24245. var editor = new Editor(createId(elm), extend({}, settings, type), self);
  24246. editors.push(editor);
  24247. editor.render();
  24248. });
  24249. });
  24250. return;
  24251. } else if (settings.selector) {
  24252. // Process global selector
  24253. each(DOM.select(settings.selector), function(elm) {
  24254. var editor = new Editor(createId(elm), settings, self);
  24255. editors.push(editor);
  24256. editor.render();
  24257. });
  24258. return;
  24259. }
  24260. // Fallback to old setting
  24261. switch (settings.mode) {
  24262. case "exact":
  24263. l = settings.elements || '';
  24264. if (l.length > 0) {
  24265. each(explode(l), function(v) {
  24266. if (DOM.get(v)) {
  24267. editor = new Editor(v, settings, self);
  24268. editors.push(editor);
  24269. editor.render();
  24270. } else {
  24271. each(document.forms, function(f) {
  24272. each(f.elements, function(e) {
  24273. if (e.name === v) {
  24274. v = 'mce_editor_' + instanceCounter++;
  24275. DOM.setAttrib(e, 'id', v);
  24276. editor = new Editor(v, settings, self);
  24277. editors.push(editor);
  24278. editor.render();
  24279. }
  24280. });
  24281. });
  24282. }
  24283. });
  24284. }
  24285. break;
  24286. case "textareas":
  24287. case "specific_textareas":
  24288. each(DOM.select('textarea'), function(elm) {
  24289. if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
  24290. return;
  24291. }
  24292. if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
  24293. editor = new Editor(createId(elm), settings, self);
  24294. editors.push(editor);
  24295. editor.render();
  24296. }
  24297. });
  24298. break;
  24299. }
  24300. // Call onInit when all editors are initialized
  24301. if (settings.oninit) {
  24302. l = co = 0;
  24303. each(editors, function(ed) {
  24304. co++;
  24305. if (!ed.initialized) {
  24306. // Wait for it
  24307. ed.on('init', function() {
  24308. l++;
  24309. // All done
  24310. if (l == co) {
  24311. execCallback(settings, 'oninit');
  24312. }
  24313. });
  24314. } else {
  24315. l++;
  24316. }
  24317. // All done
  24318. if (l == co) {
  24319. execCallback(settings, 'oninit');
  24320. }
  24321. });
  24322. }
  24323. }
  24324. self.settings = settings;
  24325. DOM.bind(window, 'ready', readyHandler);
  24326. },
  24327. /**
  24328. * Returns a editor instance by id.
  24329. *
  24330. * @method get
  24331. * @param {String/Number} id Editor instance id or index to return.
  24332. * @return {tinymce.Editor} Editor instance to return.
  24333. * @example
  24334. * // Adds an onclick event to an editor by id (shorter version)
  24335. * tinymce.get('mytextbox').on('click', function(e) {
  24336. * ed.windowManager.alert('Hello world!');
  24337. * });
  24338. *
  24339. * // Adds an onclick event to an editor by id (longer version)
  24340. * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
  24341. * ed.windowManager.alert('Hello world!');
  24342. * });
  24343. */
  24344. get: function(id) {
  24345. if (id === undefined) {
  24346. return this.editors;
  24347. }
  24348. return this.editors[id];
  24349. },
  24350. /**
  24351. * Adds an editor instance to the editor collection. This will also set it as the active editor.
  24352. *
  24353. * @method add
  24354. * @param {tinymce.Editor} editor Editor instance to add to the collection.
  24355. * @return {tinymce.Editor} The same instance that got passed in.
  24356. */
  24357. add: function(editor) {
  24358. var self = this, editors = self.editors;
  24359. // Add named and index editor instance
  24360. editors[editor.id] = editor;
  24361. editors.push(editor);
  24362. self.activeEditor = editor;
  24363. /**
  24364. * Fires when an editor is added to the EditorManager collection.
  24365. *
  24366. * @event AddEditor
  24367. * @param {Object} e Event arguments.
  24368. */
  24369. self.fire('AddEditor', {editor: editor});
  24370. if (!beforeUnloadDelegate) {
  24371. beforeUnloadDelegate = function() {
  24372. self.fire('BeforeUnload');
  24373. };
  24374. DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
  24375. }
  24376. return editor;
  24377. },
  24378. /**
  24379. * Creates an editor instance and adds it to the EditorManager collection.
  24380. *
  24381. * @method createEditor
  24382. * @param {String} id Instance id to use for editor.
  24383. * @param {Object} settings Editor instance settings.
  24384. * @return {tinymce.Editor} Editor instance that got created.
  24385. */
  24386. createEditor: function(id, settings) {
  24387. return this.add(new Editor(id, settings, this));
  24388. },
  24389. /**
  24390. * Removes a editor or editors form page.
  24391. *
  24392. * @example
  24393. * // Remove all editors bound to divs
  24394. * tinymce.remove('div');
  24395. *
  24396. * // Remove all editors bound to textareas
  24397. * tinymce.remove('textarea');
  24398. *
  24399. * // Remove all editors
  24400. * tinymce.remove();
  24401. *
  24402. * // Remove specific instance by id
  24403. * tinymce.remove('#id');
  24404. *
  24405. * @method remove
  24406. * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
  24407. * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
  24408. */
  24409. remove: function(selector) {
  24410. var self = this, i, editors = self.editors, editor, removedFromList;
  24411. // Remove all editors
  24412. if (!selector) {
  24413. for (i = editors.length - 1; i >= 0; i--) {
  24414. self.remove(editors[i]);
  24415. }
  24416. return;
  24417. }
  24418. // Remove editors by selector
  24419. if (typeof(selector) == "string") {
  24420. selector = selector.selector || selector;
  24421. each(DOM.select(selector), function(elm) {
  24422. self.remove(editors[elm.id]);
  24423. });
  24424. return;
  24425. }
  24426. // Remove specific editor
  24427. editor = selector;
  24428. // Not in the collection
  24429. if (!editors[editor.id]) {
  24430. return null;
  24431. }
  24432. delete editors[editor.id];
  24433. for (i = 0; i < editors.length; i++) {
  24434. if (editors[i] == editor) {
  24435. editors.splice(i, 1);
  24436. removedFromList = true;
  24437. break;
  24438. }
  24439. }
  24440. // Select another editor since the active one was removed
  24441. if (self.activeEditor == editor) {
  24442. self.activeEditor = editors[0];
  24443. }
  24444. /**
  24445. * Fires when an editor is removed from EditorManager collection.
  24446. *
  24447. * @event RemoveEditor
  24448. * @param {Object} e Event arguments.
  24449. */
  24450. if (removedFromList) {
  24451. self.fire('RemoveEditor', {editor: editor});
  24452. }
  24453. if (!editors.length) {
  24454. DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
  24455. }
  24456. editor.remove();
  24457. return editor;
  24458. },
  24459. /**
  24460. * Executes a specific command on the currently active editor.
  24461. *
  24462. * @method execCommand
  24463. * @param {String} c Command to perform for example Bold.
  24464. * @param {Boolean} u Optional boolean state if a UI should be presented for the command or not.
  24465. * @param {String} v Optional value parameter like for example an URL to a link.
  24466. * @return {Boolean} true/false if the command was executed or not.
  24467. */
  24468. execCommand: function(cmd, ui, value) {
  24469. var self = this, editor = self.get(value);
  24470. // Manager commands
  24471. switch (cmd) {
  24472. case "mceAddEditor":
  24473. if (!self.get(value)) {
  24474. new Editor(value, self.settings, self).render();
  24475. }
  24476. return true;
  24477. case "mceRemoveEditor":
  24478. if (editor) {
  24479. editor.remove();
  24480. }
  24481. return true;
  24482. case 'mceToggleEditor':
  24483. if (!editor) {
  24484. self.execCommand('mceAddEditor', 0, value);
  24485. return true;
  24486. }
  24487. if (editor.isHidden()) {
  24488. editor.show();
  24489. } else {
  24490. editor.hide();
  24491. }
  24492. return true;
  24493. }
  24494. // Run command on active editor
  24495. if (self.activeEditor) {
  24496. return self.activeEditor.execCommand(cmd, ui, value);
  24497. }
  24498. return false;
  24499. },
  24500. /**
  24501. * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
  24502. *
  24503. * @method triggerSave
  24504. * @example
  24505. * // Saves all contents
  24506. * tinyMCE.triggerSave();
  24507. */
  24508. triggerSave: function() {
  24509. each(this.editors, function(editor) {
  24510. editor.save();
  24511. });
  24512. },
  24513. /**
  24514. * Adds a language pack, this gets called by the loaded language files like en.js.
  24515. *
  24516. * @method addI18n
  24517. * @param {String} code Optional language code.
  24518. * @param {Object} items Name/value object with translations.
  24519. */
  24520. addI18n: function(code, items) {
  24521. I18n.add(code, items);
  24522. },
  24523. /**
  24524. * Translates the specified string using the language pack items.
  24525. *
  24526. * @method translate
  24527. * @param {String/Array/Object} text String to translate
  24528. * @return {String} Translated string.
  24529. */
  24530. translate: function(text) {
  24531. return I18n.translate(text);
  24532. }
  24533. };
  24534. extend(EditorManager, Observable);
  24535. EditorManager.setup();
  24536. // Export EditorManager as tinymce/tinymce in global namespace
  24537. window.tinymce = window.tinyMCE = EditorManager;
  24538. return EditorManager;
  24539. });
  24540. // Included from: js/tinymce/classes/LegacyInput.js
  24541. /**
  24542. * LegacyInput.js
  24543. *
  24544. * Copyright, Moxiecode Systems AB
  24545. * Released under LGPL License.
  24546. *
  24547. * License: http://www.tinymce.com/license
  24548. * Contributing: http://www.tinymce.com/contributing
  24549. */
  24550. define("tinymce/LegacyInput", [
  24551. "tinymce/EditorManager",
  24552. "tinymce/util/Tools"
  24553. ], function(EditorManager, Tools) {
  24554. var each = Tools.each, explode = Tools.explode;
  24555. EditorManager.on('AddEditor', function(e) {
  24556. var editor = e.editor;
  24557. editor.on('preInit', function() {
  24558. var filters, fontSizes, dom, settings = editor.settings;
  24559. function replaceWithSpan(node, styles) {
  24560. each(styles, function(value, name) {
  24561. if (value) {
  24562. dom.setStyle(node, name, value);
  24563. }
  24564. });
  24565. dom.rename(node, 'span');
  24566. }
  24567. function convert(e) {
  24568. dom = editor.dom;
  24569. if (settings.convert_fonts_to_spans) {
  24570. each(dom.select('font,u,strike', e.node), function(node) {
  24571. filters[node.nodeName.toLowerCase()](dom, node);
  24572. });
  24573. }
  24574. }
  24575. if (settings.inline_styles) {
  24576. fontSizes = explode(settings.font_size_legacy_values);
  24577. filters = {
  24578. font: function(dom, node) {
  24579. replaceWithSpan(node, {
  24580. backgroundColor: node.style.backgroundColor,
  24581. color: node.color,
  24582. fontFamily: node.face,
  24583. fontSize: fontSizes[parseInt(node.size, 10) - 1]
  24584. });
  24585. },
  24586. u: function(dom, node) {
  24587. replaceWithSpan(node, {
  24588. textDecoration: 'underline'
  24589. });
  24590. },
  24591. strike: function(dom, node) {
  24592. replaceWithSpan(node, {
  24593. textDecoration: 'line-through'
  24594. });
  24595. }
  24596. };
  24597. editor.on('PreProcess SetContent', convert);
  24598. }
  24599. });
  24600. });
  24601. });
  24602. // Included from: js/tinymce/classes/util/XHR.js
  24603. /**
  24604. * XHR.js
  24605. *
  24606. * Copyright, Moxiecode Systems AB
  24607. * Released under LGPL License.
  24608. *
  24609. * License: http://www.tinymce.com/license
  24610. * Contributing: http://www.tinymce.com/contributing
  24611. */
  24612. /**
  24613. * This class enables you to send XMLHTTPRequests cross browser.
  24614. * @class tinymce.util.XHR
  24615. * @static
  24616. * @example
  24617. * // Sends a low level Ajax request
  24618. * tinymce.util.XHR.send({
  24619. * url: 'someurl',
  24620. * success: function(text) {
  24621. * console.debug(text);
  24622. * }
  24623. * });
  24624. */
  24625. define("tinymce/util/XHR", [], function() {
  24626. return {
  24627. /**
  24628. * Sends a XMLHTTPRequest.
  24629. * Consult the Wiki for details on what settings this method takes.
  24630. *
  24631. * @method send
  24632. * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
  24633. */
  24634. send: function(settings) {
  24635. var xhr, count = 0;
  24636. function ready() {
  24637. if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
  24638. if (settings.success && count < 10000 && xhr.status == 200) {
  24639. settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
  24640. } else if (settings.error) {
  24641. settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
  24642. }
  24643. xhr = null;
  24644. } else {
  24645. setTimeout(ready, 10);
  24646. }
  24647. }
  24648. // Default settings
  24649. settings.scope = settings.scope || this;
  24650. settings.success_scope = settings.success_scope || settings.scope;
  24651. settings.error_scope = settings.error_scope || settings.scope;
  24652. settings.async = settings.async === false ? false : true;
  24653. settings.data = settings.data || '';
  24654. xhr = new XMLHttpRequest();
  24655. if (xhr) {
  24656. if (xhr.overrideMimeType) {
  24657. xhr.overrideMimeType(settings.content_type);
  24658. }
  24659. xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
  24660. if (settings.content_type) {
  24661. xhr.setRequestHeader('Content-Type', settings.content_type);
  24662. }
  24663. xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  24664. xhr.send(settings.data);
  24665. // Syncronous request
  24666. if (!settings.async) {
  24667. return ready();
  24668. }
  24669. // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
  24670. setTimeout(ready, 10);
  24671. }
  24672. }
  24673. };
  24674. });
  24675. // Included from: js/tinymce/classes/util/JSON.js
  24676. /**
  24677. * JSON.js
  24678. *
  24679. * Copyright, Moxiecode Systems AB
  24680. * Released under LGPL License.
  24681. *
  24682. * License: http://www.tinymce.com/license
  24683. * Contributing: http://www.tinymce.com/contributing
  24684. */
  24685. /**
  24686. * JSON parser and serializer class.
  24687. *
  24688. * @class tinymce.util.JSON
  24689. * @static
  24690. * @example
  24691. * // JSON parse a string into an object
  24692. * var obj = tinymce.util.JSON.parse(somestring);
  24693. *
  24694. * // JSON serialize a object into an string
  24695. * var str = tinymce.util.JSON.serialize(obj);
  24696. */
  24697. define("tinymce/util/JSON", [], function() {
  24698. function serialize(o, quote) {
  24699. var i, v, t, name;
  24700. quote = quote || '"';
  24701. if (o === null) {
  24702. return 'null';
  24703. }
  24704. t = typeof o;
  24705. if (t == 'string') {
  24706. v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
  24707. return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
  24708. // Make sure single quotes never get encoded inside double quotes for JSON compatibility
  24709. if (quote === '"' && a === "'") {
  24710. return a;
  24711. }
  24712. i = v.indexOf(b);
  24713. if (i + 1) {
  24714. return '\\' + v.charAt(i + 1);
  24715. }
  24716. a = b.charCodeAt().toString(16);
  24717. return '\\u' + '0000'.substring(a.length) + a;
  24718. }) + quote;
  24719. }
  24720. if (t == 'object') {
  24721. if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
  24722. for (i = 0, v = '['; i < o.length; i++) {
  24723. v += (i > 0 ? ',' : '') + serialize(o[i], quote);
  24724. }
  24725. return v + ']';
  24726. }
  24727. v = '{';
  24728. for (name in o) {
  24729. if (o.hasOwnProperty(name)) {
  24730. v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
  24731. quote + ':' + serialize(o[name], quote) : '';
  24732. }
  24733. }
  24734. return v + '}';
  24735. }
  24736. return '' + o;
  24737. }
  24738. return {
  24739. /**
  24740. * Serializes the specified object as a JSON string.
  24741. *
  24742. * @method serialize
  24743. * @param {Object} obj Object to serialize as a JSON string.
  24744. * @param {String} quote Optional quote string defaults to ".
  24745. * @return {string} JSON string serialized from input.
  24746. */
  24747. serialize: serialize,
  24748. /**
  24749. * Unserializes/parses the specified JSON string into a object.
  24750. *
  24751. * @method parse
  24752. * @param {string} s JSON String to parse into a JavaScript object.
  24753. * @return {Object} Object from input JSON string or undefined if it failed.
  24754. */
  24755. parse: function(text) {
  24756. try {
  24757. // Trick uglify JS
  24758. return window[String.fromCharCode(101) + 'val']('(' + text + ')');
  24759. } catch (ex) {
  24760. // Ignore
  24761. }
  24762. }
  24763. /**#@-*/
  24764. };
  24765. });
  24766. // Included from: js/tinymce/classes/util/JSONRequest.js
  24767. /**
  24768. * JSONRequest.js
  24769. *
  24770. * Copyright, Moxiecode Systems AB
  24771. * Released under LGPL License.
  24772. *
  24773. * License: http://www.tinymce.com/license
  24774. * Contributing: http://www.tinymce.com/contributing
  24775. */
  24776. /**
  24777. * This class enables you to use JSON-RPC to call backend methods.
  24778. *
  24779. * @class tinymce.util.JSONRequest
  24780. * @example
  24781. * var json = new tinymce.util.JSONRequest({
  24782. * url: 'somebackend.php'
  24783. * });
  24784. *
  24785. * // Send RPC call 1
  24786. * json.send({
  24787. * method: 'someMethod1',
  24788. * params: ['a', 'b'],
  24789. * success: function(result) {
  24790. * console.dir(result);
  24791. * }
  24792. * });
  24793. *
  24794. * // Send RPC call 2
  24795. * json.send({
  24796. * method: 'someMethod2',
  24797. * params: ['a', 'b'],
  24798. * success: function(result) {
  24799. * console.dir(result);
  24800. * }
  24801. * });
  24802. */
  24803. define("tinymce/util/JSONRequest", [
  24804. "tinymce/util/JSON",
  24805. "tinymce/util/XHR",
  24806. "tinymce/util/Tools"
  24807. ], function(JSON, XHR, Tools) {
  24808. var extend = Tools.extend;
  24809. function JSONRequest(settings) {
  24810. this.settings = extend({}, settings);
  24811. this.count = 0;
  24812. }
  24813. /**
  24814. * Simple helper function to send a JSON-RPC request without the need to initialize an object.
  24815. * Consult the Wiki API documentation for more details on what you can pass to this function.
  24816. *
  24817. * @method sendRPC
  24818. * @static
  24819. * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
  24820. */
  24821. JSONRequest.sendRPC = function(o) {
  24822. return new JSONRequest().send(o);
  24823. };
  24824. JSONRequest.prototype = {
  24825. /**
  24826. * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
  24827. *
  24828. * @method send
  24829. * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
  24830. */
  24831. send: function(args) {
  24832. var ecb = args.error, scb = args.success;
  24833. args = extend(this.settings, args);
  24834. args.success = function(c, x) {
  24835. c = JSON.parse(c);
  24836. if (typeof(c) == 'undefined') {
  24837. c = {
  24838. error : 'JSON Parse error.'
  24839. };
  24840. }
  24841. if (c.error) {
  24842. ecb.call(args.error_scope || args.scope, c.error, x);
  24843. } else {
  24844. scb.call(args.success_scope || args.scope, c.result);
  24845. }
  24846. };
  24847. args.error = function(ty, x) {
  24848. if (ecb) {
  24849. ecb.call(args.error_scope || args.scope, ty, x);
  24850. }
  24851. };
  24852. args.data = JSON.serialize({
  24853. id: args.id || 'c' + (this.count++),
  24854. method: args.method,
  24855. params: args.params
  24856. });
  24857. // JSON content type for Ruby on rails. Bug: #1883287
  24858. args.content_type = 'application/json';
  24859. XHR.send(args);
  24860. }
  24861. };
  24862. return JSONRequest;
  24863. });
  24864. // Included from: js/tinymce/classes/util/JSONP.js
  24865. /**
  24866. * JSONP.js
  24867. *
  24868. * Copyright, Moxiecode Systems AB
  24869. * Released under LGPL License.
  24870. *
  24871. * License: http://www.tinymce.com/license
  24872. * Contributing: http://www.tinymce.com/contributing
  24873. */
  24874. define("tinymce/util/JSONP", [
  24875. "tinymce/dom/DOMUtils"
  24876. ], function(DOMUtils) {
  24877. return {
  24878. callbacks: {},
  24879. count: 0,
  24880. send: function(settings) {
  24881. var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
  24882. var id = 'tinymce_jsonp_' + count;
  24883. self.callbacks[count] = function(json) {
  24884. dom.remove(id);
  24885. delete self.callbacks[count];
  24886. settings.callback(json);
  24887. };
  24888. dom.add(dom.doc.body, 'script', {
  24889. id: id,
  24890. src: settings.url,
  24891. type: 'text/javascript'
  24892. });
  24893. self.count++;
  24894. }
  24895. };
  24896. });
  24897. // Included from: js/tinymce/classes/util/LocalStorage.js
  24898. /**
  24899. * LocalStorage.js
  24900. *
  24901. * Copyright, Moxiecode Systems AB
  24902. * Released under LGPL License.
  24903. *
  24904. * License: http://www.tinymce.com/license
  24905. * Contributing: http://www.tinymce.com/contributing
  24906. */
  24907. /**
  24908. * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
  24909. * Storage is done using userData on IE 7 and a special serialization format. The format is designed
  24910. * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
  24911. * makes it possible to store for example HTML data.
  24912. *
  24913. * Storage format for userData:
  24914. * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
  24915. *
  24916. * For example this data key1=value1,key2=value2 would be:
  24917. * 4,key1,6,value1,4,key2,6,value2
  24918. *
  24919. * @class tinymce.util.LocalStorage
  24920. * @static
  24921. * @version 4.0
  24922. * @example
  24923. * tinymce.util.LocalStorage.setItem('key', 'value');
  24924. * var value = tinymce.util.LocalStorage.getItem('key');
  24925. */
  24926. define("tinymce/util/LocalStorage", [], function() {
  24927. var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
  24928. // Check for native support
  24929. try {
  24930. if (window.localStorage) {
  24931. return localStorage;
  24932. }
  24933. } catch (ex) {
  24934. // Ignore
  24935. }
  24936. userDataKey = "tinymce";
  24937. storageElm = document.documentElement;
  24938. hasOldIEDataSupport = !!storageElm.addBehavior;
  24939. if (hasOldIEDataSupport) {
  24940. storageElm.addBehavior('#default#userData');
  24941. }
  24942. /**
  24943. * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
  24944. */
  24945. function updateKeys() {
  24946. keys = [];
  24947. for (var key in items) {
  24948. keys.push(key);
  24949. }
  24950. LocalStorage.length = keys.length;
  24951. }
  24952. /**
  24953. * Loads the userData string and parses it into the items structure.
  24954. */
  24955. function load() {
  24956. var key, data, value, pos = 0;
  24957. items = {};
  24958. // localStorage can be disabled on WebKit/Gecko so make a dummy storage
  24959. if (!hasOldIEDataSupport) {
  24960. return;
  24961. }
  24962. function next(end) {
  24963. var value, nextPos;
  24964. nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
  24965. if (nextPos === -1 || nextPos > data.length) {
  24966. return null;
  24967. }
  24968. value = data.substring(pos, nextPos);
  24969. pos = nextPos + 1;
  24970. return value;
  24971. }
  24972. storageElm.load(userDataKey);
  24973. data = storageElm.getAttribute(userDataKey) || '';
  24974. do {
  24975. var offset = next();
  24976. if (offset === null) {
  24977. break;
  24978. }
  24979. key = next(parseInt(offset, 32) || 0);
  24980. if (key !== null) {
  24981. offset = next();
  24982. if (offset === null) {
  24983. break;
  24984. }
  24985. value = next(parseInt(offset, 32) || 0);
  24986. if (key) {
  24987. items[key] = value;
  24988. }
  24989. }
  24990. } while (key !== null);
  24991. updateKeys();
  24992. }
  24993. /**
  24994. * Saves the items structure into a the userData format.
  24995. */
  24996. function save() {
  24997. var value, data = '';
  24998. // localStorage can be disabled on WebKit/Gecko so make a dummy storage
  24999. if (!hasOldIEDataSupport) {
  25000. return;
  25001. }
  25002. for (var key in items) {
  25003. value = items[key];
  25004. data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
  25005. }
  25006. storageElm.setAttribute(userDataKey, data);
  25007. try {
  25008. storageElm.save(userDataKey);
  25009. } catch (ex) {
  25010. // Ignore disk full
  25011. }
  25012. updateKeys();
  25013. }
  25014. LocalStorage = {
  25015. /**
  25016. * Length of the number of items in storage.
  25017. *
  25018. * @property length
  25019. * @type Number
  25020. * @return {Number} Number of items in storage.
  25021. */
  25022. //length:0,
  25023. /**
  25024. * Returns the key name by index.
  25025. *
  25026. * @method key
  25027. * @param {Number} index Index of key to return.
  25028. * @return {String} Key value or null if it wasn't found.
  25029. */
  25030. key: function(index) {
  25031. return keys[index];
  25032. },
  25033. /**
  25034. * Returns the value if the specified key or null if it wasn't found.
  25035. *
  25036. * @method getItem
  25037. * @param {String} key Key of item to retrive.
  25038. * @return {String} Value of the specified item or null if it wasn't found.
  25039. */
  25040. getItem: function(key) {
  25041. return key in items ? items[key] : null;
  25042. },
  25043. /**
  25044. * Sets the value of the specified item by it's key.
  25045. *
  25046. * @method setItem
  25047. * @param {String} key Key of the item to set.
  25048. * @param {String} value Value of the item to set.
  25049. */
  25050. setItem: function(key, value) {
  25051. items[key] = "" + value;
  25052. save();
  25053. },
  25054. /**
  25055. * Removes the specified item by key.
  25056. *
  25057. * @method removeItem
  25058. * @param {String} key Key of item to remove.
  25059. */
  25060. removeItem: function(key) {
  25061. delete items[key];
  25062. save();
  25063. },
  25064. /**
  25065. * Removes all items.
  25066. *
  25067. * @method clear
  25068. */
  25069. clear: function() {
  25070. items = {};
  25071. save();
  25072. }
  25073. };
  25074. load();
  25075. return LocalStorage;
  25076. });
  25077. // Included from: js/tinymce/classes/Compat.js
  25078. /**
  25079. * Compat.js
  25080. *
  25081. * Copyright, Moxiecode Systems AB
  25082. * Released under LGPL License.
  25083. *
  25084. * License: http://www.tinymce.com/license
  25085. * Contributing: http://www.tinymce.com/contributing
  25086. */
  25087. /**
  25088. * TinyMCE core class.
  25089. *
  25090. * @static
  25091. * @class tinymce
  25092. * @borrow-members tinymce.EditorManager
  25093. * @borrow-members tinymce.util.Tools
  25094. */
  25095. define("tinymce/Compat", [
  25096. "tinymce/dom/DOMUtils",
  25097. "tinymce/dom/EventUtils",
  25098. "tinymce/dom/ScriptLoader",
  25099. "tinymce/AddOnManager",
  25100. "tinymce/util/Tools",
  25101. "tinymce/Env"
  25102. ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
  25103. var tinymce = window.tinymce;
  25104. /**
  25105. * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
  25106. * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
  25107. * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
  25108. * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
  25109. */
  25110. tinymce.DOM = DOMUtils.DOM;
  25111. tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
  25112. tinymce.PluginManager = AddOnManager.PluginManager;
  25113. tinymce.ThemeManager = AddOnManager.ThemeManager;
  25114. tinymce.dom = tinymce.dom || {};
  25115. tinymce.dom.Event = EventUtils.Event;
  25116. Tools.each(Tools, function(func, key) {
  25117. tinymce[key] = func;
  25118. });
  25119. Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
  25120. tinymce[name] = Env[name.substr(2).toLowerCase()];
  25121. });
  25122. return {};
  25123. });
  25124. // Describe the different namespaces
  25125. /**
  25126. * Root level namespace this contains classes directly releated to the TinyMCE editor.
  25127. *
  25128. * @namespace tinymce
  25129. */
  25130. /**
  25131. * Contains classes for handling the browsers DOM.
  25132. *
  25133. * @namespace tinymce.dom
  25134. */
  25135. /**
  25136. * Contains html parser and serializer logic.
  25137. *
  25138. * @namespace tinymce.html
  25139. */
  25140. /**
  25141. * Contains the different UI types such as buttons, listboxes etc.
  25142. *
  25143. * @namespace tinymce.ui
  25144. */
  25145. /**
  25146. * Contains various utility classes such as json parser, cookies etc.
  25147. *
  25148. * @namespace tinymce.util
  25149. */
  25150. // Included from: js/tinymce/classes/ui/Layout.js
  25151. /**
  25152. * Layout.js
  25153. *
  25154. * Copyright, Moxiecode Systems AB
  25155. * Released under LGPL License.
  25156. *
  25157. * License: http://www.tinymce.com/license
  25158. * Contributing: http://www.tinymce.com/contributing
  25159. */
  25160. /**
  25161. * Base layout manager class.
  25162. *
  25163. * @class tinymce.ui.Layout
  25164. */
  25165. define("tinymce/ui/Layout", [
  25166. "tinymce/util/Class",
  25167. "tinymce/util/Tools"
  25168. ], function(Class, Tools) {
  25169. "use strict";
  25170. return Class.extend({
  25171. Defaults: {
  25172. firstControlClass: 'first',
  25173. lastControlClass: 'last'
  25174. },
  25175. /**
  25176. * Constructs a layout instance with the specified settings.
  25177. *
  25178. * @constructor
  25179. * @param {Object} settings Name/value object with settings.
  25180. */
  25181. init: function(settings) {
  25182. this.settings = Tools.extend({}, this.Defaults, settings);
  25183. },
  25184. /**
  25185. * This method gets invoked before the layout renders the controls.
  25186. *
  25187. * @method preRender
  25188. * @param {tinymce.ui.Container} container Container instance to preRender.
  25189. */
  25190. preRender: function(container) {
  25191. container.addClass(this.settings.containerClass, 'body');
  25192. },
  25193. /**
  25194. * Applies layout classes to the container.
  25195. *
  25196. * @private
  25197. */
  25198. applyClasses: function(container) {
  25199. var self = this, settings = self.settings, items, firstClass, lastClass;
  25200. items = container.items().filter(':visible');
  25201. firstClass = settings.firstControlClass;
  25202. lastClass = settings.lastControlClass;
  25203. items.each(function(item) {
  25204. item.removeClass(firstClass).removeClass(lastClass);
  25205. if (settings.controlClass) {
  25206. item.addClass(settings.controlClass);
  25207. }
  25208. });
  25209. items.eq(0).addClass(firstClass);
  25210. items.eq(-1).addClass(lastClass);
  25211. },
  25212. /**
  25213. * Renders the specified container and any layout specific HTML.
  25214. *
  25215. * @method renderHtml
  25216. * @param {tinymce.ui.Container} container Container to render HTML for.
  25217. */
  25218. renderHtml: function(container) {
  25219. var self = this, settings = self.settings, items, html = '';
  25220. items = container.items();
  25221. items.eq(0).addClass(settings.firstControlClass);
  25222. items.eq(-1).addClass(settings.lastControlClass);
  25223. items.each(function(item) {
  25224. if (settings.controlClass) {
  25225. item.addClass(settings.controlClass);
  25226. }
  25227. html += item.renderHtml();
  25228. });
  25229. return html;
  25230. },
  25231. /**
  25232. * Recalculates the positions of the controls in the specified container.
  25233. *
  25234. * @method recalc
  25235. * @param {tinymce.ui.Container} container Container instance to recalc.
  25236. */
  25237. recalc: function() {
  25238. },
  25239. /**
  25240. * This method gets invoked after the layout renders the controls.
  25241. *
  25242. * @method postRender
  25243. * @param {tinymce.ui.Container} container Container instance to postRender.
  25244. */
  25245. postRender: function() {
  25246. }
  25247. });
  25248. });
  25249. // Included from: js/tinymce/classes/ui/AbsoluteLayout.js
  25250. /**
  25251. * AbsoluteLayout.js
  25252. *
  25253. * Copyright, Moxiecode Systems AB
  25254. * Released under LGPL License.
  25255. *
  25256. * License: http://www.tinymce.com/license
  25257. * Contributing: http://www.tinymce.com/contributing
  25258. */
  25259. /**
  25260. * LayoutManager for absolute positioning. This layout manager is more of
  25261. * a base class for other layouts but can be created and used directly.
  25262. *
  25263. * @-x-less AbsoluteLayout.less
  25264. * @class tinymce.ui.AbsoluteLayout
  25265. * @extends tinymce.ui.Layout
  25266. */
  25267. define("tinymce/ui/AbsoluteLayout", [
  25268. "tinymce/ui/Layout"
  25269. ], function(Layout) {
  25270. "use strict";
  25271. return Layout.extend({
  25272. Defaults: {
  25273. containerClass: 'abs-layout',
  25274. controlClass: 'abs-layout-item'
  25275. },
  25276. /**
  25277. * Recalculates the positions of the controls in the specified container.
  25278. *
  25279. * @method recalc
  25280. * @param {tinymce.ui.Container} container Container instance to recalc.
  25281. */
  25282. recalc: function(container) {
  25283. container.items().filter(':visible').each(function(ctrl) {
  25284. var settings = ctrl.settings;
  25285. ctrl.layoutRect({
  25286. x: settings.x,
  25287. y: settings.y,
  25288. w: settings.w,
  25289. h: settings.h
  25290. });
  25291. if (ctrl.recalc) {
  25292. ctrl.recalc();
  25293. }
  25294. });
  25295. },
  25296. /**
  25297. * Renders the specified container and any layout specific HTML.
  25298. *
  25299. * @method renderHtml
  25300. * @param {tinymce.ui.Container} container Container to render HTML for.
  25301. */
  25302. renderHtml: function(container) {
  25303. return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
  25304. }
  25305. });
  25306. });
  25307. // Included from: js/tinymce/classes/ui/Tooltip.js
  25308. /**
  25309. * Tooltip.js
  25310. *
  25311. * Copyright, Moxiecode Systems AB
  25312. * Released under LGPL License.
  25313. *
  25314. * License: http://www.tinymce.com/license
  25315. * Contributing: http://www.tinymce.com/contributing
  25316. */
  25317. /**
  25318. * Creates a tooltip instance.
  25319. *
  25320. * @-x-less ToolTip.less
  25321. * @class tinymce.ui.ToolTip
  25322. * @extends tinymce.ui.Control
  25323. * @mixes tinymce.ui.Movable
  25324. */
  25325. define("tinymce/ui/Tooltip", [
  25326. "tinymce/ui/Control",
  25327. "tinymce/ui/Movable"
  25328. ], function(Control, Movable) {
  25329. return Control.extend({
  25330. Mixins: [Movable],
  25331. Defaults: {
  25332. classes: 'widget tooltip tooltip-n'
  25333. },
  25334. /**
  25335. * Sets/gets the current label text.
  25336. *
  25337. * @method text
  25338. * @param {String} [text] New label text.
  25339. * @return {String|tinymce.ui.Tooltip} Current text or current label instance.
  25340. */
  25341. text: function(value) {
  25342. var self = this;
  25343. if (typeof(value) != "undefined") {
  25344. self._value = value;
  25345. if (self._rendered) {
  25346. self.getEl().lastChild.innerHTML = self.encode(value);
  25347. }
  25348. return self;
  25349. }
  25350. return self._value;
  25351. },
  25352. /**
  25353. * Renders the control as a HTML string.
  25354. *
  25355. * @method renderHtml
  25356. * @return {String} HTML representing the control.
  25357. */
  25358. renderHtml: function() {
  25359. var self = this, prefix = self.classPrefix;
  25360. return (
  25361. '<div id="' + self._id + '" class="' + self.classes() + '" role="presentation">' +
  25362. '<div class="' + prefix + 'tooltip-arrow"></div>' +
  25363. '<div class="' + prefix + 'tooltip-inner">' + self.encode(self._text) + '</div>' +
  25364. '</div>'
  25365. );
  25366. },
  25367. /**
  25368. * Repaints the control after a layout operation.
  25369. *
  25370. * @method repaint
  25371. */
  25372. repaint: function() {
  25373. var self = this, style, rect;
  25374. style = self.getEl().style;
  25375. rect = self._layoutRect;
  25376. style.left = rect.x + 'px';
  25377. style.top = rect.y + 'px';
  25378. style.zIndex = 0xFFFF + 0xFFFF;
  25379. }
  25380. });
  25381. });
  25382. // Included from: js/tinymce/classes/ui/Widget.js
  25383. /**
  25384. * Widget.js
  25385. *
  25386. * Copyright, Moxiecode Systems AB
  25387. * Released under LGPL License.
  25388. *
  25389. * License: http://www.tinymce.com/license
  25390. * Contributing: http://www.tinymce.com/contributing
  25391. */
  25392. /**
  25393. * Widget base class a widget is a control that has a tooltip and some basic states.
  25394. *
  25395. * @class tinymce.ui.Widget
  25396. * @extends tinymce.ui.Control
  25397. */
  25398. define("tinymce/ui/Widget", [
  25399. "tinymce/ui/Control",
  25400. "tinymce/ui/Tooltip"
  25401. ], function(Control, Tooltip) {
  25402. "use strict";
  25403. var tooltip;
  25404. var Widget = Control.extend({
  25405. /**
  25406. * Constructs a instance with the specified settings.
  25407. *
  25408. * @constructor
  25409. * @param {Object} settings Name/value object with settings.
  25410. * @setting {String} tooltip Tooltip text to display when hovering.
  25411. * @setting {Boolean} autofocus True if the control should be focused when rendered.
  25412. * @setting {String} text Text to display inside widget.
  25413. */
  25414. init: function(settings) {
  25415. var self = this;
  25416. self._super(settings);
  25417. settings = self.settings;
  25418. self.canFocus = true;
  25419. if (settings.tooltip && Widget.tooltips !== false) {
  25420. self.on('mouseenter', function(e) {
  25421. var tooltip = self.tooltip().moveTo(-0xFFFF);
  25422. if (e.control == self) {
  25423. var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
  25424. tooltip.toggleClass('tooltip-n', rel == 'bc-tc');
  25425. tooltip.toggleClass('tooltip-nw', rel == 'bc-tl');
  25426. tooltip.toggleClass('tooltip-ne', rel == 'bc-tr');
  25427. tooltip.moveRel(self.getEl(), rel);
  25428. } else {
  25429. tooltip.hide();
  25430. }
  25431. });
  25432. self.on('mouseleave mousedown click', function() {
  25433. self.tooltip().hide();
  25434. });
  25435. }
  25436. self.aria('label', settings.ariaLabel || settings.tooltip);
  25437. },
  25438. /**
  25439. * Returns the current tooltip instance.
  25440. *
  25441. * @method tooltip
  25442. * @return {tinymce.ui.Tooltip} Tooltip instance.
  25443. */
  25444. tooltip: function() {
  25445. if (!tooltip) {
  25446. tooltip = new Tooltip({type: 'tooltip'});
  25447. tooltip.renderTo();
  25448. }
  25449. return tooltip;
  25450. },
  25451. /**
  25452. * Sets/gets the active state of the widget.
  25453. *
  25454. * @method active
  25455. * @param {Boolean} [state] State if the control is active.
  25456. * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
  25457. */
  25458. active: function(state) {
  25459. var self = this, undef;
  25460. if (state !== undef) {
  25461. self.aria('pressed', state);
  25462. self.toggleClass('active', state);
  25463. }
  25464. return self._super(state);
  25465. },
  25466. /**
  25467. * Sets/gets the disabled state of the widget.
  25468. *
  25469. * @method disabled
  25470. * @param {Boolean} [state] State if the control is disabled.
  25471. * @return {Boolean|tinymce.ui.Widget} True/false or current widget instance.
  25472. */
  25473. disabled: function(state) {
  25474. var self = this, undef;
  25475. if (state !== undef) {
  25476. self.aria('disabled', state);
  25477. self.toggleClass('disabled', state);
  25478. }
  25479. return self._super(state);
  25480. },
  25481. /**
  25482. * Called after the control has been rendered.
  25483. *
  25484. * @method postRender
  25485. */
  25486. postRender: function() {
  25487. var self = this, settings = self.settings;
  25488. self._rendered = true;
  25489. self._super();
  25490. if (!self.parent() && (settings.width || settings.height)) {
  25491. self.initLayoutRect();
  25492. self.repaint();
  25493. }
  25494. if (settings.autofocus) {
  25495. self.focus();
  25496. }
  25497. },
  25498. /**
  25499. * Removes the current control from DOM and from UI collections.
  25500. *
  25501. * @method remove
  25502. * @return {tinymce.ui.Control} Current control instance.
  25503. */
  25504. remove: function() {
  25505. this._super();
  25506. if (tooltip) {
  25507. tooltip.remove();
  25508. tooltip = null;
  25509. }
  25510. }
  25511. });
  25512. return Widget;
  25513. });
  25514. // Included from: js/tinymce/classes/ui/Button.js
  25515. /**
  25516. * Button.js
  25517. *
  25518. * Copyright, Moxiecode Systems AB
  25519. * Released under LGPL License.
  25520. *
  25521. * License: http://www.tinymce.com/license
  25522. * Contributing: http://www.tinymce.com/contributing
  25523. */
  25524. /**
  25525. * This class is used to create buttons. You can create them directly or through the Factory.
  25526. *
  25527. * @example
  25528. * // Create and render a button to the body element
  25529. * tinymce.ui.Factory.create({
  25530. * type: 'button',
  25531. * text: 'My button'
  25532. * }).renderTo(document.body);
  25533. *
  25534. * @-x-less Button.less
  25535. * @class tinymce.ui.Button
  25536. * @extends tinymce.ui.Widget
  25537. */
  25538. define("tinymce/ui/Button", [
  25539. "tinymce/ui/Widget"
  25540. ], function(Widget) {
  25541. "use strict";
  25542. return Widget.extend({
  25543. Defaults: {
  25544. classes: "widget btn",
  25545. role: "button"
  25546. },
  25547. /**
  25548. * Constructs a new button instance with the specified settings.
  25549. *
  25550. * @constructor
  25551. * @param {Object} settings Name/value object with settings.
  25552. * @setting {String} size Size of the button small|medium|large.
  25553. * @setting {String} image Image to use for icon.
  25554. * @setting {String} icon Icon to use for button.
  25555. */
  25556. init: function(settings) {
  25557. var self = this, size;
  25558. self.on('click mousedown', function(e) {
  25559. e.preventDefault();
  25560. });
  25561. self._super(settings);
  25562. size = settings.size;
  25563. if (settings.subtype) {
  25564. self.addClass(settings.subtype);
  25565. }
  25566. if (size) {
  25567. self.addClass('btn-' + size);
  25568. }
  25569. },
  25570. /**
  25571. * Sets/gets the current button icon.
  25572. *
  25573. * @method icon
  25574. * @param {String} [icon] New icon identifier.
  25575. * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
  25576. */
  25577. icon: function(icon) {
  25578. var self = this, prefix = self.classPrefix;
  25579. if (typeof(icon) == 'undefined') {
  25580. return self.settings.icon;
  25581. }
  25582. self.settings.icon = icon;
  25583. icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  25584. if (self._rendered) {
  25585. var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
  25586. if (icon) {
  25587. if (!iconElm || iconElm != btnElm.firstChild) {
  25588. iconElm = document.createElement('i');
  25589. btnElm.insertBefore(iconElm, btnElm.firstChild);
  25590. }
  25591. iconElm.className = icon;
  25592. } else if (iconElm) {
  25593. btnElm.removeChild(iconElm);
  25594. }
  25595. self.text(self._text); // Set text again to fix whitespace between icon + text
  25596. }
  25597. return self;
  25598. },
  25599. /**
  25600. * Repaints the button for example after it's been resizes by a layout engine.
  25601. *
  25602. * @method repaint
  25603. */
  25604. repaint: function() {
  25605. var btnStyle = this.getEl().firstChild.style;
  25606. btnStyle.width = btnStyle.height = "100%";
  25607. this._super();
  25608. },
  25609. /**
  25610. * Sets/gets the current button text.
  25611. *
  25612. * @method text
  25613. * @param {String} [text] New button text.
  25614. * @return {String|tinymce.ui.Button} Current text or current Button instance.
  25615. */
  25616. text: function(text) {
  25617. var self = this;
  25618. if (self._rendered) {
  25619. var textNode = self.getEl().lastChild.lastChild;
  25620. if (textNode) {
  25621. textNode.data = self.translate(text);
  25622. }
  25623. }
  25624. return self._super(text);
  25625. },
  25626. /**
  25627. * Renders the control as a HTML string.
  25628. *
  25629. * @method renderHtml
  25630. * @return {String} HTML representing the control.
  25631. */
  25632. renderHtml: function() {
  25633. var self = this, id = self._id, prefix = self.classPrefix;
  25634. var icon = self.settings.icon, image;
  25635. image = self.settings.image;
  25636. if (image) {
  25637. icon = 'none';
  25638. // Support for [high dpi, low dpi] image sources
  25639. if (typeof image != "string") {
  25640. image = window.getSelection ? image[0] : image[1];
  25641. }
  25642. image = ' style="background-image: url(\'' + image + '\')"';
  25643. } else {
  25644. image = '';
  25645. }
  25646. icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
  25647. return (
  25648. '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
  25649. '<button role="presentation" type="button" tabindex="-1">' +
  25650. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  25651. (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
  25652. '</button>' +
  25653. '</div>'
  25654. );
  25655. }
  25656. });
  25657. });
  25658. // Included from: js/tinymce/classes/ui/ButtonGroup.js
  25659. /**
  25660. * ButtonGroup.js
  25661. *
  25662. * Copyright, Moxiecode Systems AB
  25663. * Released under LGPL License.
  25664. *
  25665. * License: http://www.tinymce.com/license
  25666. * Contributing: http://www.tinymce.com/contributing
  25667. */
  25668. /**
  25669. * This control enables you to put multiple buttons into a group. This is
  25670. * useful when you want to combine similar toolbar buttons into a group.
  25671. *
  25672. * @example
  25673. * // Create and render a buttongroup with two buttons to the body element
  25674. * tinymce.ui.Factory.create({
  25675. * type: 'buttongroup',
  25676. * items: [
  25677. * {text: 'Button A'},
  25678. * {text: 'Button B'}
  25679. * ]
  25680. * }).renderTo(document.body);
  25681. *
  25682. * @-x-less ButtonGroup.less
  25683. * @class tinymce.ui.ButtonGroup
  25684. * @extends tinymce.ui.Container
  25685. */
  25686. define("tinymce/ui/ButtonGroup", [
  25687. "tinymce/ui/Container"
  25688. ], function(Container) {
  25689. "use strict";
  25690. return Container.extend({
  25691. Defaults: {
  25692. defaultType: 'button',
  25693. role: 'group'
  25694. },
  25695. /**
  25696. * Renders the control as a HTML string.
  25697. *
  25698. * @method renderHtml
  25699. * @return {String} HTML representing the control.
  25700. */
  25701. renderHtml: function() {
  25702. var self = this, layout = self._layout;
  25703. self.addClass('btn-group');
  25704. self.preRender();
  25705. layout.preRender(self);
  25706. return (
  25707. '<div id="' + self._id + '" class="' + self.classes() + '">' +
  25708. '<div id="' + self._id + '-body">' +
  25709. (self.settings.html || '') + layout.renderHtml(self) +
  25710. '</div>' +
  25711. '</div>'
  25712. );
  25713. }
  25714. });
  25715. });
  25716. // Included from: js/tinymce/classes/ui/Checkbox.js
  25717. /**
  25718. * Checkbox.js
  25719. *
  25720. * Copyright, Moxiecode Systems AB
  25721. * Released under LGPL License.
  25722. *
  25723. * License: http://www.tinymce.com/license
  25724. * Contributing: http://www.tinymce.com/contributing
  25725. */
  25726. /**
  25727. * This control creates a custom checkbox.
  25728. *
  25729. * @example
  25730. * // Create and render a checkbox to the body element
  25731. * tinymce.ui.Factory.create({
  25732. * type: 'checkbox',
  25733. * checked: true,
  25734. * text: 'My checkbox'
  25735. * }).renderTo(document.body);
  25736. *
  25737. * @-x-less Checkbox.less
  25738. * @class tinymce.ui.Checkbox
  25739. * @extends tinymce.ui.Widget
  25740. */
  25741. define("tinymce/ui/Checkbox", [
  25742. "tinymce/ui/Widget"
  25743. ], function(Widget) {
  25744. "use strict";
  25745. return Widget.extend({
  25746. Defaults: {
  25747. classes: "checkbox",
  25748. role: "checkbox",
  25749. checked: false
  25750. },
  25751. /**
  25752. * Constructs a new Checkbox instance with the specified settings.
  25753. *
  25754. * @constructor
  25755. * @param {Object} settings Name/value object with settings.
  25756. * @setting {Boolean} checked True if the checkbox should be checked by default.
  25757. */
  25758. init: function(settings) {
  25759. var self = this;
  25760. self._super(settings);
  25761. self.on('click mousedown', function(e) {
  25762. e.preventDefault();
  25763. });
  25764. self.on('click', function(e) {
  25765. e.preventDefault();
  25766. if (!self.disabled()) {
  25767. self.checked(!self.checked());
  25768. }
  25769. });
  25770. self.checked(self.settings.checked);
  25771. },
  25772. /**
  25773. * Getter/setter function for the checked state.
  25774. *
  25775. * @method checked
  25776. * @param {Boolean} [state] State to be set.
  25777. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
  25778. */
  25779. checked: function(state) {
  25780. var self = this;
  25781. if (typeof state != "undefined") {
  25782. if (state) {
  25783. self.addClass('checked');
  25784. } else {
  25785. self.removeClass('checked');
  25786. }
  25787. self._checked = state;
  25788. self.aria('checked', state);
  25789. return self;
  25790. }
  25791. return self._checked;
  25792. },
  25793. /**
  25794. * Getter/setter function for the value state.
  25795. *
  25796. * @method value
  25797. * @param {Boolean} [state] State to be set.
  25798. * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
  25799. */
  25800. value: function(state) {
  25801. return this.checked(state);
  25802. },
  25803. /**
  25804. * Renders the control as a HTML string.
  25805. *
  25806. * @method renderHtml
  25807. * @return {String} HTML representing the control.
  25808. */
  25809. renderHtml: function() {
  25810. var self = this, id = self._id, prefix = self.classPrefix;
  25811. return (
  25812. '<div id="' + id + '" class="' + self.classes() + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
  25813. '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
  25814. '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self._text) + '</span>' +
  25815. '</div>'
  25816. );
  25817. }
  25818. });
  25819. });
  25820. // Included from: js/tinymce/classes/ui/PanelButton.js
  25821. /**
  25822. * PanelButton.js
  25823. *
  25824. * Copyright, Moxiecode Systems AB
  25825. * Released under LGPL License.
  25826. *
  25827. * License: http://www.tinymce.com/license
  25828. * Contributing: http://www.tinymce.com/contributing
  25829. */
  25830. /**
  25831. * Creates a new panel button.
  25832. *
  25833. * @class tinymce.ui.PanelButton
  25834. * @extends tinymce.ui.Button
  25835. */
  25836. define("tinymce/ui/PanelButton", [
  25837. "tinymce/ui/Button",
  25838. "tinymce/ui/FloatPanel"
  25839. ], function(Button, FloatPanel) {
  25840. "use strict";
  25841. return Button.extend({
  25842. /**
  25843. * Shows the panel for the button.
  25844. *
  25845. * @method showPanel
  25846. */
  25847. showPanel: function() {
  25848. var self = this, settings = self.settings;
  25849. self.active(true);
  25850. if (!self.panel) {
  25851. var panelSettings = settings.panel;
  25852. // Wrap panel in grid layout if type if specified
  25853. // This makes it possible to add forms or other containers directly in the panel option
  25854. if (panelSettings.type) {
  25855. panelSettings = {
  25856. layout: 'grid',
  25857. items: panelSettings
  25858. };
  25859. }
  25860. panelSettings.role = panelSettings.role || 'dialog';
  25861. panelSettings.popover = true;
  25862. panelSettings.autohide = true;
  25863. panelSettings.ariaRoot = true;
  25864. self.panel = new FloatPanel(panelSettings).on('hide', function() {
  25865. self.active(false);
  25866. }).on('cancel', function(e) {
  25867. e.stopPropagation();
  25868. self.focus();
  25869. self.hidePanel();
  25870. }).parent(self).renderTo(self.getContainerElm());
  25871. self.panel.fire('show');
  25872. self.panel.reflow();
  25873. } else {
  25874. self.panel.show();
  25875. }
  25876. self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
  25877. },
  25878. /**
  25879. * Hides the panel for the button.
  25880. *
  25881. * @method hidePanel
  25882. */
  25883. hidePanel: function() {
  25884. var self = this;
  25885. if (self.panel) {
  25886. self.panel.hide();
  25887. }
  25888. },
  25889. /**
  25890. * Called after the control has been rendered.
  25891. *
  25892. * @method postRender
  25893. */
  25894. postRender: function() {
  25895. var self = this;
  25896. self.aria('haspopup', true);
  25897. self.on('click', function(e) {
  25898. if (e.control === self) {
  25899. if (self.panel && self.panel.visible()) {
  25900. self.hidePanel();
  25901. } else {
  25902. self.showPanel();
  25903. self.panel.focus(!!e.aria);
  25904. }
  25905. }
  25906. });
  25907. return self._super();
  25908. }
  25909. });
  25910. });
  25911. // Included from: js/tinymce/classes/ui/ColorButton.js
  25912. /**
  25913. * ColorButton.js
  25914. *
  25915. * Copyright, Moxiecode Systems AB
  25916. * Released under LGPL License.
  25917. *
  25918. * License: http://www.tinymce.com/license
  25919. * Contributing: http://www.tinymce.com/contributing
  25920. */
  25921. /**
  25922. * This class creates a color button control. This is a split button in which the main
  25923. * button has a visual representation of the currently selected color. When clicked
  25924. * the caret button displays a color picker, allowing the user to select a new color.
  25925. *
  25926. * @-x-less ColorButton.less
  25927. * @class tinymce.ui.ColorButton
  25928. * @extends tinymce.ui.PanelButton
  25929. */
  25930. define("tinymce/ui/ColorButton", [
  25931. "tinymce/ui/PanelButton",
  25932. "tinymce/dom/DOMUtils"
  25933. ], function(PanelButton, DomUtils) {
  25934. "use strict";
  25935. var DOM = DomUtils.DOM;
  25936. return PanelButton.extend({
  25937. /**
  25938. * Constructs a new ColorButton instance with the specified settings.
  25939. *
  25940. * @constructor
  25941. * @param {Object} settings Name/value object with settings.
  25942. */
  25943. init: function(settings) {
  25944. this._super(settings);
  25945. this.addClass('colorbutton');
  25946. },
  25947. /**
  25948. * Getter/setter for the current color.
  25949. *
  25950. * @method color
  25951. * @param {String} [color] Color to set.
  25952. * @return {String|tinymce.ui.ColorButton} Current color or current instance.
  25953. */
  25954. color: function(color) {
  25955. if (color) {
  25956. this._color = color;
  25957. this.getEl('preview').style.backgroundColor = color;
  25958. return this;
  25959. }
  25960. return this._color;
  25961. },
  25962. /**
  25963. * Renders the control as a HTML string.
  25964. *
  25965. * @method renderHtml
  25966. * @return {String} HTML representing the control.
  25967. */
  25968. renderHtml: function() {
  25969. var self = this, id = self._id, prefix = self.classPrefix;
  25970. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  25971. var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
  25972. return (
  25973. '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
  25974. '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
  25975. (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
  25976. '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
  25977. (self._text ? (icon ? ' ' : '') + (self._text) : '') +
  25978. '</button>' +
  25979. '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  25980. ' <i class="' + prefix + 'caret"></i>' +
  25981. '</button>' +
  25982. '</div>'
  25983. );
  25984. },
  25985. /**
  25986. * Called after the control has been rendered.
  25987. *
  25988. * @method postRender
  25989. */
  25990. postRender: function() {
  25991. var self = this, onClickHandler = self.settings.onclick;
  25992. self.on('click', function(e) {
  25993. if (e.aria && e.aria.key == 'down') {
  25994. return;
  25995. }
  25996. if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
  25997. e.stopImmediatePropagation();
  25998. onClickHandler.call(self, e);
  25999. }
  26000. });
  26001. delete self.settings.onclick;
  26002. return self._super();
  26003. }
  26004. });
  26005. });
  26006. // Included from: js/tinymce/classes/ui/ComboBox.js
  26007. /**
  26008. * ComboBox.js
  26009. *
  26010. * Copyright, Moxiecode Systems AB
  26011. * Released under LGPL License.
  26012. *
  26013. * License: http://www.tinymce.com/license
  26014. * Contributing: http://www.tinymce.com/contributing
  26015. */
  26016. /**
  26017. * This class creates a combobox control. Select box that you select a value from or
  26018. * type a value into.
  26019. *
  26020. * @-x-less ComboBox.less
  26021. * @class tinymce.ui.ComboBox
  26022. * @extends tinymce.ui.Widget
  26023. */
  26024. define("tinymce/ui/ComboBox", [
  26025. "tinymce/ui/Widget",
  26026. "tinymce/ui/Factory",
  26027. "tinymce/ui/DomUtils"
  26028. ], function(Widget, Factory, DomUtils) {
  26029. "use strict";
  26030. return Widget.extend({
  26031. /**
  26032. * Constructs a new control instance with the specified settings.
  26033. *
  26034. * @constructor
  26035. * @param {Object} settings Name/value object with settings.
  26036. * @setting {String} placeholder Placeholder text to display.
  26037. */
  26038. init: function(settings) {
  26039. var self = this;
  26040. self._super(settings);
  26041. self.addClass('combobox');
  26042. self.subinput = true;
  26043. self.ariaTarget = 'inp'; // TODO: Figure out a better way
  26044. settings = self.settings;
  26045. settings.menu = settings.menu || settings.values;
  26046. if (settings.menu) {
  26047. settings.icon = 'caret';
  26048. }
  26049. self.on('click', function(e) {
  26050. var elm = e.target, root = self.getEl();
  26051. while (elm && elm != root) {
  26052. if (elm.id && elm.id.indexOf('-open') != -1) {
  26053. self.fire('action');
  26054. if (settings.menu) {
  26055. self.showMenu();
  26056. if (e.aria) {
  26057. self.menu.items()[0].focus();
  26058. }
  26059. }
  26060. }
  26061. elm = elm.parentNode;
  26062. }
  26063. });
  26064. // TODO: Rework this
  26065. self.on('keydown', function(e) {
  26066. if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
  26067. self.parents().reverse().each(function(ctrl) {
  26068. e.preventDefault();
  26069. self.fire('change');
  26070. if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  26071. ctrl.fire('submit', {data: ctrl.toJSON()});
  26072. return false;
  26073. }
  26074. });
  26075. }
  26076. });
  26077. if (settings.placeholder) {
  26078. self.addClass('placeholder');
  26079. self.on('focusin', function() {
  26080. if (!self._hasOnChange) {
  26081. DomUtils.on(self.getEl('inp'), 'change', function() {
  26082. self.fire('change');
  26083. });
  26084. self._hasOnChange = true;
  26085. }
  26086. if (self.hasClass('placeholder')) {
  26087. self.getEl('inp').value = '';
  26088. self.removeClass('placeholder');
  26089. }
  26090. });
  26091. self.on('focusout', function() {
  26092. if (self.value().length === 0) {
  26093. self.getEl('inp').value = settings.placeholder;
  26094. self.addClass('placeholder');
  26095. }
  26096. });
  26097. }
  26098. },
  26099. showMenu: function() {
  26100. var self = this, settings = self.settings, menu;
  26101. if (!self.menu) {
  26102. menu = settings.menu || [];
  26103. // Is menu array then auto constuct menu control
  26104. if (menu.length) {
  26105. menu = {
  26106. type: 'menu',
  26107. items: menu
  26108. };
  26109. } else {
  26110. menu.type = menu.type || 'menu';
  26111. }
  26112. self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
  26113. self.fire('createmenu');
  26114. self.menu.reflow();
  26115. self.menu.on('cancel', function(e) {
  26116. if (e.control === self.menu) {
  26117. self.focus();
  26118. }
  26119. });
  26120. self.menu.on('show hide', function(e) {
  26121. e.control.items().each(function(ctrl) {
  26122. ctrl.active(ctrl.value() == self.value());
  26123. });
  26124. }).fire('show');
  26125. self.menu.on('select', function(e) {
  26126. self.value(e.control.value());
  26127. });
  26128. self.on('focusin', function(e) {
  26129. if (e.target.tagName.toUpperCase() == 'INPUT') {
  26130. self.menu.hide();
  26131. }
  26132. });
  26133. self.aria('expanded', true);
  26134. }
  26135. self.menu.show();
  26136. self.menu.layoutRect({w: self.layoutRect().w});
  26137. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  26138. },
  26139. /**
  26140. * Getter/setter function for the control value.
  26141. *
  26142. * @method value
  26143. * @param {String} [value] Value to be set.
  26144. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
  26145. */
  26146. value: function(value) {
  26147. var self = this;
  26148. if (typeof(value) != "undefined") {
  26149. self._value = value;
  26150. self.removeClass('placeholder');
  26151. if (self._rendered) {
  26152. self.getEl('inp').value = value;
  26153. }
  26154. return self;
  26155. }
  26156. if (self._rendered) {
  26157. value = self.getEl('inp').value;
  26158. if (value != self.settings.placeholder) {
  26159. return value;
  26160. }
  26161. return '';
  26162. }
  26163. return self._value;
  26164. },
  26165. /**
  26166. * Getter/setter function for the disabled state.
  26167. *
  26168. * @method value
  26169. * @param {Boolean} [state] State to be set.
  26170. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
  26171. */
  26172. disabled: function(state) {
  26173. var self = this;
  26174. if (self._rendered && typeof(state) != 'undefined') {
  26175. self.getEl('inp').disabled = state;
  26176. }
  26177. return self._super(state);
  26178. },
  26179. /**
  26180. * Focuses the input area of the control.
  26181. *
  26182. * @method focus
  26183. */
  26184. focus: function() {
  26185. this.getEl('inp').focus();
  26186. },
  26187. /**
  26188. * Repaints the control after a layout operation.
  26189. *
  26190. * @method repaint
  26191. */
  26192. repaint: function() {
  26193. var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
  26194. var width, lineHeight;
  26195. if (openElm) {
  26196. width = rect.w - DomUtils.getSize(openElm).width - 10;
  26197. } else {
  26198. width = rect.w - 10;
  26199. }
  26200. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  26201. var doc = document;
  26202. if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  26203. lineHeight = (self.layoutRect().h - 2) + 'px';
  26204. }
  26205. DomUtils.css(elm.firstChild, {
  26206. width: width,
  26207. lineHeight: lineHeight
  26208. });
  26209. self._super();
  26210. return self;
  26211. },
  26212. /**
  26213. * Post render method. Called after the control has been rendered to the target.
  26214. *
  26215. * @method postRender
  26216. * @return {tinymce.ui.ComboBox} Current combobox instance.
  26217. */
  26218. postRender: function() {
  26219. var self = this;
  26220. DomUtils.on(this.getEl('inp'), 'change', function() {
  26221. self.fire('change');
  26222. });
  26223. return self._super();
  26224. },
  26225. remove: function() {
  26226. DomUtils.off(this.getEl('inp'));
  26227. this._super();
  26228. },
  26229. /**
  26230. * Renders the control as a HTML string.
  26231. *
  26232. * @method renderHtml
  26233. * @return {String} HTML representing the control.
  26234. */
  26235. renderHtml: function() {
  26236. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
  26237. var value = settings.value || settings.placeholder || '';
  26238. var icon, text, openBtnHtml = '', extraAttrs = '';
  26239. if ("spellcheck" in settings) {
  26240. extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  26241. }
  26242. if (settings.maxLength) {
  26243. extraAttrs += ' maxlength="' + settings.maxLength + '"';
  26244. }
  26245. if (settings.size) {
  26246. extraAttrs += ' size="' + settings.size + '"';
  26247. }
  26248. if (settings.subtype) {
  26249. extraAttrs += ' type="' + settings.subtype + '"';
  26250. }
  26251. if (self.disabled()) {
  26252. extraAttrs += ' disabled="disabled"';
  26253. }
  26254. icon = settings.icon;
  26255. if (icon && icon != 'caret') {
  26256. icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
  26257. }
  26258. text = self._text;
  26259. if (icon || text) {
  26260. openBtnHtml = (
  26261. '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
  26262. '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
  26263. (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
  26264. (text ? (icon ? ' ' : '') + text : '') +
  26265. '</button>' +
  26266. '</div>'
  26267. );
  26268. self.addClass('has-open');
  26269. }
  26270. return (
  26271. '<div id="' + id + '" class="' + self.classes() + '">' +
  26272. '<input id="' + id + '-inp" class="' + prefix + 'textbox ' + prefix + 'placeholder" value="' +
  26273. value + '" hidefocus="1"' + extraAttrs + ' />' +
  26274. openBtnHtml +
  26275. '</div>'
  26276. );
  26277. }
  26278. });
  26279. });
  26280. // Included from: js/tinymce/classes/ui/Path.js
  26281. /**
  26282. * Path.js
  26283. *
  26284. * Copyright, Moxiecode Systems AB
  26285. * Released under LGPL License.
  26286. *
  26287. * License: http://www.tinymce.com/license
  26288. * Contributing: http://www.tinymce.com/contributing
  26289. */
  26290. /**
  26291. * Creates a new path control.
  26292. *
  26293. * @-x-less Path.less
  26294. * @class tinymce.ui.Path
  26295. * @extends tinymce.ui.Widget
  26296. */
  26297. define("tinymce/ui/Path", [
  26298. "tinymce/ui/Widget"
  26299. ], function(Widget) {
  26300. "use strict";
  26301. return Widget.extend({
  26302. /**
  26303. * Constructs a instance with the specified settings.
  26304. *
  26305. * @constructor
  26306. * @param {Object} settings Name/value object with settings.
  26307. * @setting {String} delimiter Delimiter to display between items in path.
  26308. */
  26309. init: function(settings) {
  26310. var self = this;
  26311. if (!settings.delimiter) {
  26312. settings.delimiter = '\u00BB';
  26313. }
  26314. self._super(settings);
  26315. self.addClass('path');
  26316. self.canFocus = true;
  26317. self.on('click', function(e) {
  26318. var index, target = e.target;
  26319. if ((index = target.getAttribute('data-index'))) {
  26320. self.fire('select', {value: self.data()[index], index: index});
  26321. }
  26322. });
  26323. },
  26324. /**
  26325. * Focuses the current control.
  26326. *
  26327. * @method focus
  26328. * @return {tinymce.ui.Control} Current control instance.
  26329. */
  26330. focus: function() {
  26331. var self = this;
  26332. self.getEl().firstChild.focus();
  26333. return self;
  26334. },
  26335. /**
  26336. * Sets/gets the data to be used for the path.
  26337. *
  26338. * @method data
  26339. * @param {Array} data Array with items name is rendered to path.
  26340. */
  26341. data: function(data) {
  26342. var self = this;
  26343. if (typeof(data) !== "undefined") {
  26344. self._data = data;
  26345. self.update();
  26346. return self;
  26347. }
  26348. return self._data;
  26349. },
  26350. /**
  26351. * Updated the path.
  26352. *
  26353. * @private
  26354. */
  26355. update: function() {
  26356. this.innerHtml(this._getPathHtml());
  26357. },
  26358. /**
  26359. * Called after the control has been rendered.
  26360. *
  26361. * @method postRender
  26362. */
  26363. postRender: function() {
  26364. var self = this;
  26365. self._super();
  26366. self.data(self.settings.data);
  26367. },
  26368. /**
  26369. * Renders the control as a HTML string.
  26370. *
  26371. * @method renderHtml
  26372. * @return {String} HTML representing the control.
  26373. */
  26374. renderHtml: function() {
  26375. var self = this;
  26376. return (
  26377. '<div id="' + self._id + '" class="' + self.classes() + '">' +
  26378. self._getPathHtml() +
  26379. '</div>'
  26380. );
  26381. },
  26382. _getPathHtml: function() {
  26383. var self = this, parts = self._data || [], i, l, html = '', prefix = self.classPrefix;
  26384. for (i = 0, l = parts.length; i < l; i++) {
  26385. html += (
  26386. (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
  26387. '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
  26388. i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
  26389. );
  26390. }
  26391. if (!html) {
  26392. html = '<div class="' + prefix + 'path-item">\u00a0</div>';
  26393. }
  26394. return html;
  26395. }
  26396. });
  26397. });
  26398. // Included from: js/tinymce/classes/ui/ElementPath.js
  26399. /**
  26400. * ElementPath.js
  26401. *
  26402. * Copyright, Moxiecode Systems AB
  26403. * Released under LGPL License.
  26404. *
  26405. * License: http://www.tinymce.com/license
  26406. * Contributing: http://www.tinymce.com/contributing
  26407. */
  26408. /**
  26409. * This control creates an path for the current selections parent elements in TinyMCE.
  26410. *
  26411. * @class tinymce.ui.ElementPath
  26412. * @extends tinymce.ui.Path
  26413. */
  26414. define("tinymce/ui/ElementPath", [
  26415. "tinymce/ui/Path",
  26416. "tinymce/EditorManager"
  26417. ], function(Path, EditorManager) {
  26418. return Path.extend({
  26419. /**
  26420. * Post render method. Called after the control has been rendered to the target.
  26421. *
  26422. * @method postRender
  26423. * @return {tinymce.ui.ElementPath} Current combobox instance.
  26424. */
  26425. postRender: function() {
  26426. var self = this, editor = EditorManager.activeEditor;
  26427. function isHidden(elm) {
  26428. if (elm.nodeType === 1) {
  26429. if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
  26430. return true;
  26431. }
  26432. if (elm.getAttribute('data-mce-type') === 'bookmark') {
  26433. return true;
  26434. }
  26435. }
  26436. return false;
  26437. }
  26438. self.on('select', function(e) {
  26439. var parents = [], node, body = editor.getBody();
  26440. editor.focus();
  26441. node = editor.selection.getStart();
  26442. while (node && node != body) {
  26443. if (!isHidden(node)) {
  26444. parents.push(node);
  26445. }
  26446. node = node.parentNode;
  26447. }
  26448. editor.selection.select(parents[parents.length - 1 - e.index]);
  26449. editor.nodeChanged();
  26450. });
  26451. editor.on('nodeChange', function(e) {
  26452. var parents = [], selectionParents = e.parents, i = selectionParents.length;
  26453. while (i--) {
  26454. if (selectionParents[i].nodeType == 1 && !isHidden(selectionParents[i])) {
  26455. var args = editor.fire('ResolveName', {
  26456. name: selectionParents[i].nodeName.toLowerCase(),
  26457. target: selectionParents[i]
  26458. });
  26459. parents.push({name: args.name});
  26460. }
  26461. }
  26462. self.data(parents);
  26463. });
  26464. return self._super();
  26465. }
  26466. });
  26467. });
  26468. // Included from: js/tinymce/classes/ui/FormItem.js
  26469. /**
  26470. * FormItem.js
  26471. *
  26472. * Copyright, Moxiecode Systems AB
  26473. * Released under LGPL License.
  26474. *
  26475. * License: http://www.tinymce.com/license
  26476. * Contributing: http://www.tinymce.com/contributing
  26477. */
  26478. /**
  26479. * This class is a container created by the form element with
  26480. * a label and control item.
  26481. *
  26482. * @class tinymce.ui.FormItem
  26483. * @extends tinymce.ui.Container
  26484. * @setting {String} label Label to display for the form item.
  26485. */
  26486. define("tinymce/ui/FormItem", [
  26487. "tinymce/ui/Container"
  26488. ], function(Container) {
  26489. "use strict";
  26490. return Container.extend({
  26491. Defaults: {
  26492. layout: 'flex',
  26493. align: 'center',
  26494. defaults: {
  26495. flex: 1
  26496. }
  26497. },
  26498. /**
  26499. * Renders the control as a HTML string.
  26500. *
  26501. * @method renderHtml
  26502. * @return {String} HTML representing the control.
  26503. */
  26504. renderHtml: function() {
  26505. var self = this, layout = self._layout, prefix = self.classPrefix;
  26506. self.addClass('formitem');
  26507. layout.preRender(self);
  26508. return (
  26509. '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  26510. (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
  26511. self.settings.title + '</div>') : '') +
  26512. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  26513. (self.settings.html || '') + layout.renderHtml(self) +
  26514. '</div>' +
  26515. '</div>'
  26516. );
  26517. }
  26518. });
  26519. });
  26520. // Included from: js/tinymce/classes/ui/Form.js
  26521. /**
  26522. * Form.js
  26523. *
  26524. * Copyright, Moxiecode Systems AB
  26525. * Released under LGPL License.
  26526. *
  26527. * License: http://www.tinymce.com/license
  26528. * Contributing: http://www.tinymce.com/contributing
  26529. */
  26530. /**
  26531. * This class creates a form container. A form container has the ability
  26532. * to automatically wrap items in tinymce.ui.FormItem instances.
  26533. *
  26534. * Each FormItem instance is a container for the label and the item.
  26535. *
  26536. * @example
  26537. * tinymce.ui.Factory.create({
  26538. * type: 'form',
  26539. * items: [
  26540. * {type: 'textbox', label: 'My text box'}
  26541. * ]
  26542. * }).renderTo(document.body);
  26543. *
  26544. * @class tinymce.ui.Form
  26545. * @extends tinymce.ui.Container
  26546. */
  26547. define("tinymce/ui/Form", [
  26548. "tinymce/ui/Container",
  26549. "tinymce/ui/FormItem"
  26550. ], function(Container, FormItem) {
  26551. "use strict";
  26552. return Container.extend({
  26553. Defaults: {
  26554. containerCls: 'form',
  26555. layout: 'flex',
  26556. direction: 'column',
  26557. align: 'stretch',
  26558. flex: 1,
  26559. padding: 20,
  26560. labelGap: 30,
  26561. spacing: 10,
  26562. callbacks: {
  26563. submit: function() {
  26564. this.submit();
  26565. }
  26566. }
  26567. },
  26568. /**
  26569. * This method gets invoked before the control is rendered.
  26570. *
  26571. * @method preRender
  26572. */
  26573. preRender: function() {
  26574. var self = this, items = self.items();
  26575. // Wrap any labeled items in FormItems
  26576. items.each(function(ctrl) {
  26577. var formItem, label = ctrl.settings.label;
  26578. if (label) {
  26579. formItem = new FormItem({
  26580. layout: 'flex',
  26581. autoResize: "overflow",
  26582. defaults: {flex: 1},
  26583. items: [
  26584. {type: 'label', id: ctrl._id + '-l', text: label, flex: 0, forId: ctrl._id, disabled: ctrl.disabled()}
  26585. ]
  26586. });
  26587. formItem.type = 'formitem';
  26588. ctrl.aria('labelledby', ctrl._id + '-l');
  26589. if (typeof(ctrl.settings.flex) == "undefined") {
  26590. ctrl.settings.flex = 1;
  26591. }
  26592. self.replace(ctrl, formItem);
  26593. formItem.add(ctrl);
  26594. }
  26595. });
  26596. },
  26597. /**
  26598. * Recalcs label widths.
  26599. *
  26600. * @private
  26601. */
  26602. recalcLabels: function() {
  26603. var self = this, maxLabelWidth = 0, labels = [], i, labelGap;
  26604. if (self.settings.labelGapCalc === false) {
  26605. return;
  26606. }
  26607. self.items().filter('formitem').each(function(item) {
  26608. var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
  26609. maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
  26610. labels.push(labelCtrl);
  26611. });
  26612. labelGap = self.settings.labelGap || 0;
  26613. i = labels.length;
  26614. while (i--) {
  26615. labels[i].settings.minWidth = maxLabelWidth + labelGap;
  26616. }
  26617. },
  26618. /**
  26619. * Getter/setter for the visibility state.
  26620. *
  26621. * @method visible
  26622. * @param {Boolean} [state] True/false state to show/hide.
  26623. * @return {tinymce.ui.Form|Boolean} True/false state or current control.
  26624. */
  26625. visible: function(state) {
  26626. var val = this._super(state);
  26627. if (state === true && this._rendered) {
  26628. this.recalcLabels();
  26629. }
  26630. return val;
  26631. },
  26632. /**
  26633. * Fires a submit event with the serialized form.
  26634. *
  26635. * @method submit
  26636. * @return {Object} Event arguments object.
  26637. */
  26638. submit: function() {
  26639. return this.fire('submit', {data: this.toJSON()});
  26640. },
  26641. /**
  26642. * Post render method. Called after the control has been rendered to the target.
  26643. *
  26644. * @method postRender
  26645. * @return {tinymce.ui.ComboBox} Current combobox instance.
  26646. */
  26647. postRender: function() {
  26648. var self = this;
  26649. self._super();
  26650. self.recalcLabels();
  26651. self.fromJSON(self.settings.data);
  26652. }
  26653. });
  26654. });
  26655. // Included from: js/tinymce/classes/ui/FieldSet.js
  26656. /**
  26657. * FieldSet.js
  26658. *
  26659. * Copyright, Moxiecode Systems AB
  26660. * Released under LGPL License.
  26661. *
  26662. * License: http://www.tinymce.com/license
  26663. * Contributing: http://www.tinymce.com/contributing
  26664. */
  26665. /**
  26666. * This class creates fieldset containers.
  26667. *
  26668. * @-x-less FieldSet.less
  26669. * @class tinymce.ui.FieldSet
  26670. * @extends tinymce.ui.Form
  26671. */
  26672. define("tinymce/ui/FieldSet", [
  26673. "tinymce/ui/Form"
  26674. ], function(Form) {
  26675. "use strict";
  26676. return Form.extend({
  26677. Defaults: {
  26678. containerCls: 'fieldset',
  26679. layout: 'flex',
  26680. direction: 'column',
  26681. align: 'stretch',
  26682. flex: 1,
  26683. padding: "25 15 5 15",
  26684. labelGap: 30,
  26685. spacing: 10,
  26686. border: 1
  26687. },
  26688. /**
  26689. * Renders the control as a HTML string.
  26690. *
  26691. * @method renderHtml
  26692. * @return {String} HTML representing the control.
  26693. */
  26694. renderHtml: function() {
  26695. var self = this, layout = self._layout, prefix = self.classPrefix;
  26696. self.preRender();
  26697. layout.preRender(self);
  26698. return (
  26699. '<fieldset id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  26700. (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
  26701. self.settings.title + '</legend>') : '') +
  26702. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  26703. (self.settings.html || '') + layout.renderHtml(self) +
  26704. '</div>' +
  26705. '</fieldset>'
  26706. );
  26707. }
  26708. });
  26709. });
  26710. // Included from: js/tinymce/classes/ui/FilePicker.js
  26711. /**
  26712. * FilePicker.js
  26713. *
  26714. * Copyright, Moxiecode Systems AB
  26715. * Released under LGPL License.
  26716. *
  26717. * License: http://www.tinymce.com/license
  26718. * Contributing: http://www.tinymce.com/contributing
  26719. */
  26720. /*global tinymce:true */
  26721. /**
  26722. * This class creates a file picker control.
  26723. *
  26724. * @class tinymce.ui.FilePicker
  26725. * @extends tinymce.ui.ComboBox
  26726. */
  26727. define("tinymce/ui/FilePicker", [
  26728. "tinymce/ui/ComboBox"
  26729. ], function(ComboBox) {
  26730. "use strict";
  26731. return ComboBox.extend({
  26732. /**
  26733. * Constructs a new control instance with the specified settings.
  26734. *
  26735. * @constructor
  26736. * @param {Object} settings Name/value object with settings.
  26737. */
  26738. init: function(settings) {
  26739. var self = this, editor = tinymce.activeEditor, fileBrowserCallback;
  26740. settings.spellcheck = false;
  26741. fileBrowserCallback = editor.settings.file_browser_callback;
  26742. if (fileBrowserCallback) {
  26743. settings.icon = 'browse';
  26744. settings.onaction = function() {
  26745. fileBrowserCallback(
  26746. self.getEl('inp').id,
  26747. self.getEl('inp').value,
  26748. settings.filetype,
  26749. window
  26750. );
  26751. };
  26752. }
  26753. self._super(settings);
  26754. }
  26755. });
  26756. });
  26757. // Included from: js/tinymce/classes/ui/FitLayout.js
  26758. /**
  26759. * FitLayout.js
  26760. *
  26761. * Copyright, Moxiecode Systems AB
  26762. * Released under LGPL License.
  26763. *
  26764. * License: http://www.tinymce.com/license
  26765. * Contributing: http://www.tinymce.com/contributing
  26766. */
  26767. /**
  26768. * This layout manager will resize the control to be the size of it's parent container.
  26769. * In other words width: 100% and height: 100%.
  26770. *
  26771. * @-x-less FitLayout.less
  26772. * @class tinymce.ui.FitLayout
  26773. * @extends tinymce.ui.AbsoluteLayout
  26774. */
  26775. define("tinymce/ui/FitLayout", [
  26776. "tinymce/ui/AbsoluteLayout"
  26777. ], function(AbsoluteLayout) {
  26778. "use strict";
  26779. return AbsoluteLayout.extend({
  26780. /**
  26781. * Recalculates the positions of the controls in the specified container.
  26782. *
  26783. * @method recalc
  26784. * @param {tinymce.ui.Container} container Container instance to recalc.
  26785. */
  26786. recalc: function(container) {
  26787. var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox();
  26788. container.items().filter(':visible').each(function(ctrl) {
  26789. ctrl.layoutRect({
  26790. x: paddingBox.left,
  26791. y: paddingBox.top,
  26792. w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
  26793. h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
  26794. });
  26795. if (ctrl.recalc) {
  26796. ctrl.recalc();
  26797. }
  26798. });
  26799. }
  26800. });
  26801. });
  26802. // Included from: js/tinymce/classes/ui/FlexLayout.js
  26803. /**
  26804. * FlexLayout.js
  26805. *
  26806. * Copyright, Moxiecode Systems AB
  26807. * Released under LGPL License.
  26808. *
  26809. * License: http://www.tinymce.com/license
  26810. * Contributing: http://www.tinymce.com/contributing
  26811. */
  26812. /**
  26813. * This layout manager works similar to the CSS flex box.
  26814. *
  26815. * @setting {String} direction row|row-reverse|column|column-reverse
  26816. * @setting {Number} flex A positive-number to flex by.
  26817. * @setting {String} align start|end|center|stretch
  26818. * @setting {String} pack start|end|justify
  26819. *
  26820. * @class tinymce.ui.FlexLayout
  26821. * @extends tinymce.ui.AbsoluteLayout
  26822. */
  26823. define("tinymce/ui/FlexLayout", [
  26824. "tinymce/ui/AbsoluteLayout"
  26825. ], function(AbsoluteLayout) {
  26826. "use strict";
  26827. return AbsoluteLayout.extend({
  26828. /**
  26829. * Recalculates the positions of the controls in the specified container.
  26830. *
  26831. * @method recalc
  26832. * @param {tinymce.ui.Container} container Container instance to recalc.
  26833. */
  26834. recalc: function(container) {
  26835. // A ton of variables, needs to be in the same scope for performance
  26836. var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
  26837. var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
  26838. var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
  26839. var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
  26840. var alignDeltaSizeName, alignContentSizeName;
  26841. var max = Math.max, min = Math.min;
  26842. // Get container items, properties and settings
  26843. items = container.items().filter(':visible');
  26844. contLayoutRect = container.layoutRect();
  26845. contPaddingBox = container._paddingBox;
  26846. contSettings = container.settings;
  26847. direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
  26848. align = contSettings.align;
  26849. pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
  26850. spacing = contSettings.spacing || 0;
  26851. if (direction == "row-reversed" || direction == "column-reverse") {
  26852. items = items.set(items.toArray().reverse());
  26853. direction = direction.split('-')[0];
  26854. }
  26855. // Setup axis variable name for row/column direction since the calculations is the same
  26856. if (direction == "column") {
  26857. posName = "y";
  26858. sizeName = "h";
  26859. minSizeName = "minH";
  26860. maxSizeName = "maxH";
  26861. innerSizeName = "innerH";
  26862. beforeName = 'top';
  26863. deltaSizeName = "deltaH";
  26864. contentSizeName = "contentH";
  26865. alignBeforeName = "left";
  26866. alignSizeName = "w";
  26867. alignAxisName = "x";
  26868. alignInnerSizeName = "innerW";
  26869. alignMinSizeName = "minW";
  26870. alignAfterName = "right";
  26871. alignDeltaSizeName = "deltaW";
  26872. alignContentSizeName = "contentW";
  26873. } else {
  26874. posName = "x";
  26875. sizeName = "w";
  26876. minSizeName = "minW";
  26877. maxSizeName = "maxW";
  26878. innerSizeName = "innerW";
  26879. beforeName = 'left';
  26880. deltaSizeName = "deltaW";
  26881. contentSizeName = "contentW";
  26882. alignBeforeName = "top";
  26883. alignSizeName = "h";
  26884. alignAxisName = "y";
  26885. alignInnerSizeName = "innerH";
  26886. alignMinSizeName = "minH";
  26887. alignAfterName = "bottom";
  26888. alignDeltaSizeName = "deltaH";
  26889. alignContentSizeName = "contentH";
  26890. }
  26891. // Figure out total flex, availableSpace and collect any max size elements
  26892. availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
  26893. maxAlignEndPos = totalFlex = 0;
  26894. for (i = 0, l = items.length; i < l; i++) {
  26895. ctrl = items[i];
  26896. ctrlLayoutRect = ctrl.layoutRect();
  26897. ctrlSettings = ctrl.settings;
  26898. flex = ctrlSettings.flex;
  26899. availableSpace -= (i < l - 1 ? spacing : 0);
  26900. if (flex > 0) {
  26901. totalFlex += flex;
  26902. // Flexed item has a max size then we need to check if we will hit that size
  26903. if (ctrlLayoutRect[maxSizeName]) {
  26904. maxSizeItems.push(ctrl);
  26905. }
  26906. ctrlLayoutRect.flex = flex;
  26907. }
  26908. availableSpace -= ctrlLayoutRect[minSizeName];
  26909. // Calculate the align end position to be used to check for overflow/underflow
  26910. size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
  26911. if (size > maxAlignEndPos) {
  26912. maxAlignEndPos = size;
  26913. }
  26914. }
  26915. // Calculate minW/minH
  26916. rect = {};
  26917. if (availableSpace < 0) {
  26918. rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  26919. } else {
  26920. rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  26921. }
  26922. rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
  26923. rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
  26924. rect[alignContentSizeName] = maxAlignEndPos;
  26925. rect.minW = min(rect.minW, contLayoutRect.maxW);
  26926. rect.minH = min(rect.minH, contLayoutRect.maxH);
  26927. rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
  26928. rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
  26929. // Resize container container if minSize was changed
  26930. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  26931. rect.w = rect.minW;
  26932. rect.h = rect.minH;
  26933. container.layoutRect(rect);
  26934. this.recalc(container);
  26935. // Forced recalc for example if items are hidden/shown
  26936. if (container._lastRect === null) {
  26937. var parentCtrl = container.parent();
  26938. if (parentCtrl) {
  26939. parentCtrl._lastRect = null;
  26940. parentCtrl.recalc();
  26941. }
  26942. }
  26943. return;
  26944. }
  26945. // Handle max size elements, check if they will become to wide with current options
  26946. ratio = availableSpace / totalFlex;
  26947. for (i = 0, l = maxSizeItems.length; i < l; i++) {
  26948. ctrl = maxSizeItems[i];
  26949. ctrlLayoutRect = ctrl.layoutRect();
  26950. maxSize = ctrlLayoutRect[maxSizeName];
  26951. size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
  26952. if (size > maxSize) {
  26953. availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
  26954. totalFlex -= ctrlLayoutRect.flex;
  26955. ctrlLayoutRect.flex = 0;
  26956. ctrlLayoutRect.maxFlexSize = maxSize;
  26957. } else {
  26958. ctrlLayoutRect.maxFlexSize = 0;
  26959. }
  26960. }
  26961. // Setup new ratio, target layout rect, start position
  26962. ratio = availableSpace / totalFlex;
  26963. pos = contPaddingBox[beforeName];
  26964. rect = {};
  26965. // Handle pack setting moves the start position to end, center
  26966. if (totalFlex === 0) {
  26967. if (pack == "end") {
  26968. pos = availableSpace + contPaddingBox[beforeName];
  26969. } else if (pack == "center") {
  26970. pos = Math.round(
  26971. (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
  26972. ) + contPaddingBox[beforeName];
  26973. if (pos < 0) {
  26974. pos = contPaddingBox[beforeName];
  26975. }
  26976. } else if (pack == "justify") {
  26977. pos = contPaddingBox[beforeName];
  26978. spacing = Math.floor(availableSpace / (items.length - 1));
  26979. }
  26980. }
  26981. // Default aligning (start) the other ones needs to be calculated while doing the layout
  26982. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  26983. // Start laying out controls
  26984. for (i = 0, l = items.length; i < l; i++) {
  26985. ctrl = items[i];
  26986. ctrlLayoutRect = ctrl.layoutRect();
  26987. size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
  26988. // Align the control on the other axis
  26989. if (align === "center") {
  26990. rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
  26991. } else if (align === "stretch") {
  26992. rect[alignSizeName] = max(
  26993. ctrlLayoutRect[alignMinSizeName] || 0,
  26994. contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
  26995. );
  26996. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  26997. } else if (align === "end") {
  26998. rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
  26999. }
  27000. // Calculate new size based on flex
  27001. if (ctrlLayoutRect.flex > 0) {
  27002. size += ctrlLayoutRect.flex * ratio;
  27003. }
  27004. rect[sizeName] = size;
  27005. rect[posName] = pos;
  27006. ctrl.layoutRect(rect);
  27007. // Recalculate containers
  27008. if (ctrl.recalc) {
  27009. ctrl.recalc();
  27010. }
  27011. // Move x/y position
  27012. pos += size + spacing;
  27013. }
  27014. }
  27015. });
  27016. });
  27017. // Included from: js/tinymce/classes/ui/FlowLayout.js
  27018. /**
  27019. * FlowLayout.js
  27020. *
  27021. * Copyright, Moxiecode Systems AB
  27022. * Released under LGPL License.
  27023. *
  27024. * License: http://www.tinymce.com/license
  27025. * Contributing: http://www.tinymce.com/contributing
  27026. */
  27027. /**
  27028. * This layout manager will place the controls by using the browsers native layout.
  27029. *
  27030. * @-x-less FlowLayout.less
  27031. * @class tinymce.ui.FlowLayout
  27032. * @extends tinymce.ui.Layout
  27033. */
  27034. define("tinymce/ui/FlowLayout", [
  27035. "tinymce/ui/Layout"
  27036. ], function(Layout) {
  27037. return Layout.extend({
  27038. Defaults: {
  27039. containerClass: 'flow-layout',
  27040. controlClass: 'flow-layout-item',
  27041. endClass : 'break'
  27042. },
  27043. /**
  27044. * Recalculates the positions of the controls in the specified container.
  27045. *
  27046. * @method recalc
  27047. * @param {tinymce.ui.Container} container Container instance to recalc.
  27048. */
  27049. recalc: function(container) {
  27050. container.items().filter(':visible').each(function(ctrl) {
  27051. if (ctrl.recalc) {
  27052. ctrl.recalc();
  27053. }
  27054. });
  27055. }
  27056. });
  27057. });
  27058. // Included from: js/tinymce/classes/ui/FormatControls.js
  27059. /**
  27060. * FormatControls.js
  27061. *
  27062. * Copyright, Moxiecode Systems AB
  27063. * Released under LGPL License.
  27064. *
  27065. * License: http://www.tinymce.com/license
  27066. * Contributing: http://www.tinymce.com/contributing
  27067. */
  27068. /**
  27069. * Internal class containing all TinyMCE specific control types such as
  27070. * format listboxes, fontlist boxes, toolbar buttons etc.
  27071. *
  27072. * @class tinymce.ui.FormatControls
  27073. */
  27074. define("tinymce/ui/FormatControls", [
  27075. "tinymce/ui/Control",
  27076. "tinymce/ui/Widget",
  27077. "tinymce/ui/FloatPanel",
  27078. "tinymce/util/Tools",
  27079. "tinymce/EditorManager",
  27080. "tinymce/Env"
  27081. ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
  27082. var each = Tools.each;
  27083. EditorManager.on('AddEditor', function(e) {
  27084. if (e.editor.rtl) {
  27085. Control.rtl = true;
  27086. }
  27087. registerControls(e.editor);
  27088. });
  27089. Control.translate = function(text) {
  27090. return EditorManager.translate(text);
  27091. };
  27092. Widget.tooltips = !Env.iOS;
  27093. function registerControls(editor) {
  27094. var formatMenu;
  27095. function createListBoxChangeHandler(items, formatName) {
  27096. return function() {
  27097. var self = this;
  27098. editor.on('nodeChange', function(e) {
  27099. var formatter = editor.formatter;
  27100. var value = null;
  27101. each(e.parents, function(node) {
  27102. each(items, function(item) {
  27103. if (formatName) {
  27104. if (formatter.matchNode(node, formatName, {value: item.value})) {
  27105. value = item.value;
  27106. }
  27107. } else {
  27108. if (formatter.matchNode(node, item.value)) {
  27109. value = item.value;
  27110. }
  27111. }
  27112. if (value) {
  27113. return false;
  27114. }
  27115. });
  27116. if (value) {
  27117. return false;
  27118. }
  27119. });
  27120. self.value(value);
  27121. });
  27122. };
  27123. }
  27124. function createFormats(formats) {
  27125. formats = formats.split(';');
  27126. var i = formats.length;
  27127. while (i--) {
  27128. formats[i] = formats[i].split('=');
  27129. }
  27130. return formats;
  27131. }
  27132. function createFormatMenu() {
  27133. var count = 0, newFormats = [];
  27134. var defaultStyleFormats = [
  27135. {title: 'Headings', items: [
  27136. {title: 'Heading 1', format: 'h1'},
  27137. {title: 'Heading 2', format: 'h2'},
  27138. {title: 'Heading 3', format: 'h3'},
  27139. {title: 'Heading 4', format: 'h4'},
  27140. {title: 'Heading 5', format: 'h5'},
  27141. {title: 'Heading 6', format: 'h6'}
  27142. ]},
  27143. {title: 'Inline', items: [
  27144. {title: 'Bold', icon: 'bold', format: 'bold'},
  27145. {title: 'Italic', icon: 'italic', format: 'italic'},
  27146. {title: 'Underline', icon: 'underline', format: 'underline'},
  27147. {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
  27148. {title: 'Superscript', icon: 'superscript', format: 'superscript'},
  27149. {title: 'Subscript', icon: 'subscript', format: 'subscript'},
  27150. {title: 'Code', icon: 'code', format: 'code'}
  27151. ]},
  27152. {title: 'Blocks', items: [
  27153. {title: 'Paragraph', format: 'p'},
  27154. {title: 'Blockquote', format: 'blockquote'},
  27155. {title: 'Div', format: 'div'},
  27156. {title: 'Pre', format: 'pre'}
  27157. ]},
  27158. {title: 'Alignment', items: [
  27159. {title: 'Left', icon: 'alignleft', format: 'alignleft'},
  27160. {title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
  27161. {title: 'Right', icon: 'alignright', format: 'alignright'},
  27162. {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
  27163. ]}
  27164. ];
  27165. function createMenu(formats) {
  27166. var menu = [];
  27167. if (!formats) {
  27168. return;
  27169. }
  27170. each(formats, function(format) {
  27171. var menuItem = {
  27172. text: format.title,
  27173. icon: format.icon
  27174. };
  27175. if (format.items) {
  27176. menuItem.menu = createMenu(format.items);
  27177. } else {
  27178. var formatName = format.format || "custom" + count++;
  27179. if (!format.format) {
  27180. format.name = formatName;
  27181. newFormats.push(format);
  27182. }
  27183. menuItem.format = formatName;
  27184. }
  27185. menu.push(menuItem);
  27186. });
  27187. return menu;
  27188. }
  27189. function createStylesMenu() {
  27190. var menu;
  27191. if (editor.settings.style_formats_merge) {
  27192. if (editor.settings.style_formats) {
  27193. menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
  27194. } else {
  27195. menu = createMenu(defaultStyleFormats);
  27196. }
  27197. } else {
  27198. menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
  27199. }
  27200. return menu;
  27201. }
  27202. editor.on('init', function() {
  27203. each(newFormats, function(format) {
  27204. editor.formatter.register(format.name, format);
  27205. });
  27206. });
  27207. return {
  27208. type: 'menu',
  27209. items: createStylesMenu(),
  27210. onPostRender: function(e) {
  27211. editor.fire('renderFormatsMenu', {control: e.control});
  27212. },
  27213. itemDefaults: {
  27214. preview: true,
  27215. textStyle: function() {
  27216. if (this.settings.format) {
  27217. return editor.formatter.getCssText(this.settings.format);
  27218. }
  27219. },
  27220. onPostRender: function() {
  27221. var self = this, formatName = this.settings.format;
  27222. if (formatName) {
  27223. self.parent().on('show', function() {
  27224. self.disabled(!editor.formatter.canApply(formatName));
  27225. self.active(editor.formatter.match(formatName));
  27226. });
  27227. }
  27228. },
  27229. onclick: function() {
  27230. if (this.settings.format) {
  27231. toggleFormat(this.settings.format);
  27232. }
  27233. }
  27234. }
  27235. };
  27236. }
  27237. formatMenu = createFormatMenu();
  27238. // Simple format controls <control/format>:<UI text>
  27239. each({
  27240. bold: 'Bold',
  27241. italic: 'Italic',
  27242. underline: 'Underline',
  27243. strikethrough: 'Strikethrough',
  27244. subscript: 'Subscript',
  27245. superscript: 'Superscript'
  27246. }, function(text, name) {
  27247. editor.addButton(name, {
  27248. tooltip: text,
  27249. onPostRender: function() {
  27250. var self = this;
  27251. // TODO: Fix this
  27252. if (editor.formatter) {
  27253. editor.formatter.formatChanged(name, function(state) {
  27254. self.active(state);
  27255. });
  27256. } else {
  27257. editor.on('init', function() {
  27258. editor.formatter.formatChanged(name, function(state) {
  27259. self.active(state);
  27260. });
  27261. });
  27262. }
  27263. },
  27264. onclick: function() {
  27265. toggleFormat(name);
  27266. }
  27267. });
  27268. });
  27269. // Simple command controls <control>:[<UI text>,<Command>]
  27270. each({
  27271. outdent: ['Decrease indent', 'Outdent'],
  27272. indent: ['Increase indent', 'Indent'],
  27273. cut: ['Cut', 'Cut'],
  27274. copy: ['Copy', 'Copy'],
  27275. paste: ['Paste', 'Paste'],
  27276. help: ['Help', 'mceHelp'],
  27277. selectall: ['Select all', 'SelectAll'],
  27278. hr: ['Insert horizontal rule', 'InsertHorizontalRule'],
  27279. removeformat: ['Clear formatting', 'RemoveFormat'],
  27280. visualaid: ['Visual aids', 'mceToggleVisualAid'],
  27281. newdocument: ['New document', 'mceNewDocument']
  27282. }, function(item, name) {
  27283. editor.addButton(name, {
  27284. tooltip: item[0],
  27285. cmd: item[1]
  27286. });
  27287. });
  27288. // Simple command controls with format state
  27289. each({
  27290. blockquote: ['Blockquote', 'mceBlockQuote'],
  27291. numlist: ['Numbered list', 'InsertOrderedList'],
  27292. bullist: ['Bullet list', 'InsertUnorderedList'],
  27293. subscript: ['Subscript', 'Subscript'],
  27294. superscript: ['Superscript', 'Superscript'],
  27295. alignleft: ['Align left', 'JustifyLeft'],
  27296. aligncenter: ['Align center', 'JustifyCenter'],
  27297. alignright: ['Align right', 'JustifyRight'],
  27298. alignjustify: ['Justify', 'JustifyFull']
  27299. }, function(item, name) {
  27300. editor.addButton(name, {
  27301. tooltip: item[0],
  27302. cmd: item[1],
  27303. onPostRender: function() {
  27304. var self = this;
  27305. // TODO: Fix this
  27306. if (editor.formatter) {
  27307. editor.formatter.formatChanged(name, function(state) {
  27308. self.active(state);
  27309. });
  27310. } else {
  27311. editor.on('init', function() {
  27312. editor.formatter.formatChanged(name, function(state) {
  27313. self.active(state);
  27314. });
  27315. });
  27316. }
  27317. }
  27318. });
  27319. });
  27320. function hasUndo() {
  27321. return editor.undoManager ? editor.undoManager.hasUndo() : false;
  27322. }
  27323. function hasRedo() {
  27324. return editor.undoManager ? editor.undoManager.hasRedo() : false;
  27325. }
  27326. function toggleUndoState() {
  27327. var self = this;
  27328. self.disabled(!hasUndo());
  27329. editor.on('Undo Redo AddUndo TypingUndo', function() {
  27330. self.disabled(!hasUndo());
  27331. });
  27332. }
  27333. function toggleRedoState() {
  27334. var self = this;
  27335. self.disabled(!hasRedo());
  27336. editor.on('Undo Redo AddUndo TypingUndo', function() {
  27337. self.disabled(!hasRedo());
  27338. });
  27339. }
  27340. function toggleVisualAidState() {
  27341. var self = this;
  27342. editor.on('VisualAid', function(e) {
  27343. self.active(e.hasVisual);
  27344. });
  27345. self.active(editor.hasVisual);
  27346. }
  27347. editor.addButton('undo', {
  27348. tooltip: 'Undo',
  27349. onPostRender: toggleUndoState,
  27350. cmd: 'undo'
  27351. });
  27352. editor.addButton('redo', {
  27353. tooltip: 'Redo',
  27354. onPostRender: toggleRedoState,
  27355. cmd: 'redo'
  27356. });
  27357. editor.addMenuItem('newdocument', {
  27358. text: 'New document',
  27359. shortcut: 'Ctrl+N',
  27360. icon: 'newdocument',
  27361. cmd: 'mceNewDocument'
  27362. });
  27363. editor.addMenuItem('undo', {
  27364. text: 'Undo',
  27365. icon: 'undo',
  27366. shortcut: 'Ctrl+Z',
  27367. onPostRender: toggleUndoState,
  27368. cmd: 'undo'
  27369. });
  27370. editor.addMenuItem('redo', {
  27371. text: 'Redo',
  27372. icon: 'redo',
  27373. shortcut: 'Ctrl+Y',
  27374. onPostRender: toggleRedoState,
  27375. cmd: 'redo'
  27376. });
  27377. editor.addMenuItem('visualaid', {
  27378. text: 'Visual aids',
  27379. selectable: true,
  27380. onPostRender: toggleVisualAidState,
  27381. cmd: 'mceToggleVisualAid'
  27382. });
  27383. each({
  27384. cut: ['Cut', 'Cut', 'Ctrl+X'],
  27385. copy: ['Copy', 'Copy', 'Ctrl+C'],
  27386. paste: ['Paste', 'Paste', 'Ctrl+V'],
  27387. selectall: ['Select all', 'SelectAll', 'Ctrl+A'],
  27388. bold: ['Bold', 'Bold', 'Ctrl+B'],
  27389. italic: ['Italic', 'Italic', 'Ctrl+I'],
  27390. underline: ['Underline', 'Underline'],
  27391. strikethrough: ['Strikethrough', 'Strikethrough'],
  27392. subscript: ['Subscript', 'Subscript'],
  27393. superscript: ['Superscript', 'Superscript'],
  27394. removeformat: ['Clear formatting', 'RemoveFormat']
  27395. }, function(item, name) {
  27396. editor.addMenuItem(name, {
  27397. text: item[0],
  27398. icon: name,
  27399. shortcut: item[2],
  27400. cmd: item[1]
  27401. });
  27402. });
  27403. editor.on('mousedown', function() {
  27404. FloatPanel.hideAll();
  27405. });
  27406. function toggleFormat(fmt) {
  27407. if (fmt.control) {
  27408. fmt = fmt.control.value();
  27409. }
  27410. if (fmt) {
  27411. editor.execCommand('mceToggleFormat', false, fmt);
  27412. }
  27413. }
  27414. editor.addButton('styleselect', {
  27415. type: 'menubutton',
  27416. text: 'Formats',
  27417. menu: formatMenu
  27418. });
  27419. editor.addButton('formatselect', function() {
  27420. var items = [], blocks = createFormats(editor.settings.block_formats ||
  27421. 'Paragraph=p;' +
  27422. 'Address=address;' +
  27423. 'Pre=pre;' +
  27424. 'Heading 1=h1;' +
  27425. 'Heading 2=h2;' +
  27426. 'Heading 3=h3;' +
  27427. 'Heading 4=h4;' +
  27428. 'Heading 5=h5;' +
  27429. 'Heading 6=h6'
  27430. );
  27431. each(blocks, function(block) {
  27432. items.push({
  27433. text: block[0],
  27434. value: block[1],
  27435. textStyle: function() {
  27436. return editor.formatter.getCssText(block[1]);
  27437. }
  27438. });
  27439. });
  27440. return {
  27441. type: 'listbox',
  27442. text: blocks[0][0],
  27443. values: items,
  27444. fixedWidth: true,
  27445. onselect: toggleFormat,
  27446. onPostRender: createListBoxChangeHandler(items)
  27447. };
  27448. });
  27449. editor.addButton('fontselect', function() {
  27450. var defaultFontsFormats =
  27451. 'Andale Mono=andale mono,times;' +
  27452. 'Arial=arial,helvetica,sans-serif;' +
  27453. 'Arial Black=arial black,avant garde;' +
  27454. 'Book Antiqua=book antiqua,palatino;' +
  27455. 'Comic Sans MS=comic sans ms,sans-serif;' +
  27456. 'Courier New=courier new,courier;' +
  27457. 'Georgia=georgia,palatino;' +
  27458. 'Helvetica=helvetica;' +
  27459. 'Impact=impact,chicago;' +
  27460. 'Symbol=symbol;' +
  27461. 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
  27462. 'Terminal=terminal,monaco;' +
  27463. 'Times New Roman=times new roman,times;' +
  27464. 'Trebuchet MS=trebuchet ms,geneva;' +
  27465. 'Verdana=verdana,geneva;' +
  27466. 'Webdings=webdings;' +
  27467. 'Wingdings=wingdings,zapf dingbats';
  27468. var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
  27469. each(fonts, function(font) {
  27470. items.push({
  27471. text: {raw: font[0]},
  27472. value: font[1],
  27473. textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
  27474. });
  27475. });
  27476. return {
  27477. type: 'listbox',
  27478. text: 'Font Family',
  27479. tooltip: 'Font Family',
  27480. values: items,
  27481. fixedWidth: true,
  27482. onPostRender: createListBoxChangeHandler(items, 'fontname'),
  27483. onselect: function(e) {
  27484. if (e.control.settings.value) {
  27485. editor.execCommand('FontName', false, e.control.settings.value);
  27486. }
  27487. }
  27488. };
  27489. });
  27490. editor.addButton('fontsizeselect', function() {
  27491. var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
  27492. var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
  27493. each(fontsize_formats.split(' '), function(item) {
  27494. items.push({text: item, value: item});
  27495. });
  27496. return {
  27497. type: 'listbox',
  27498. text: 'Font Sizes',
  27499. tooltip: 'Font Sizes',
  27500. values: items,
  27501. fixedWidth: true,
  27502. onPostRender: createListBoxChangeHandler(items, 'fontsize'),
  27503. onclick: function(e) {
  27504. if (e.control.settings.value) {
  27505. editor.execCommand('FontSize', false, e.control.settings.value);
  27506. }
  27507. }
  27508. };
  27509. });
  27510. editor.addMenuItem('formats', {
  27511. text: 'Formats',
  27512. menu: formatMenu
  27513. });
  27514. }
  27515. });
  27516. // Included from: js/tinymce/classes/ui/GridLayout.js
  27517. /**
  27518. * GridLayout.js
  27519. *
  27520. * Copyright, Moxiecode Systems AB
  27521. * Released under LGPL License.
  27522. *
  27523. * License: http://www.tinymce.com/license
  27524. * Contributing: http://www.tinymce.com/contributing
  27525. */
  27526. /**
  27527. * This layout manager places controls in a grid.
  27528. *
  27529. * @setting {Number} spacing Spacing between controls.
  27530. * @setting {Number} spacingH Horizontal spacing between controls.
  27531. * @setting {Number} spacingV Vertical spacing between controls.
  27532. * @setting {Number} columns Number of columns to use.
  27533. * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
  27534. * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
  27535. * @setting {String} pack start|end
  27536. *
  27537. * @class tinymce.ui.GridLayout
  27538. * @extends tinymce.ui.AbsoluteLayout
  27539. */
  27540. define("tinymce/ui/GridLayout", [
  27541. "tinymce/ui/AbsoluteLayout"
  27542. ], function(AbsoluteLayout) {
  27543. "use strict";
  27544. return AbsoluteLayout.extend({
  27545. /**
  27546. * Recalculates the positions of the controls in the specified container.
  27547. *
  27548. * @method recalc
  27549. * @param {tinymce.ui.Container} container Container instance to recalc.
  27550. */
  27551. recalc: function(container) {
  27552. var settings = container.settings, rows, cols, items, contLayoutRect, width, height, rect,
  27553. ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
  27554. colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight;
  27555. // Get layout settings
  27556. settings = container.settings;
  27557. items = container.items().filter(':visible');
  27558. contLayoutRect = container.layoutRect();
  27559. cols = settings.columns || Math.ceil(Math.sqrt(items.length));
  27560. rows = Math.ceil(items.length / cols);
  27561. spacingH = settings.spacingH || settings.spacing || 0;
  27562. spacingV = settings.spacingV || settings.spacing || 0;
  27563. alignH = settings.alignH || settings.align;
  27564. alignV = settings.alignV || settings.align;
  27565. contPaddingBox = container._paddingBox;
  27566. if (alignH && typeof(alignH) == "string") {
  27567. alignH = [alignH];
  27568. }
  27569. if (alignV && typeof(alignV) == "string") {
  27570. alignV = [alignV];
  27571. }
  27572. // Zero padd columnWidths
  27573. for (x = 0; x < cols; x++) {
  27574. colWidths.push(0);
  27575. }
  27576. // Zero padd rowHeights
  27577. for (y = 0; y < rows; y++) {
  27578. rowHeights.push(0);
  27579. }
  27580. // Calculate columnWidths and rowHeights
  27581. for (y = 0; y < rows; y++) {
  27582. for (x = 0; x < cols; x++) {
  27583. ctrl = items[y * cols + x];
  27584. // Out of bounds
  27585. if (!ctrl) {
  27586. break;
  27587. }
  27588. ctrlLayoutRect = ctrl.layoutRect();
  27589. ctrlMinWidth = ctrlLayoutRect.minW;
  27590. ctrlMinHeight = ctrlLayoutRect.minH;
  27591. colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
  27592. rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
  27593. }
  27594. }
  27595. // Calculate maxX
  27596. availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
  27597. for (maxX = 0, x = 0; x < cols; x++) {
  27598. maxX += colWidths[x] + (x > 0 ? spacingH : 0);
  27599. availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
  27600. }
  27601. // Calculate maxY
  27602. availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
  27603. for (maxY = 0, y = 0; y < rows; y++) {
  27604. maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
  27605. availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
  27606. }
  27607. maxX += contPaddingBox.left + contPaddingBox.right;
  27608. maxY += contPaddingBox.top + contPaddingBox.bottom;
  27609. // Calculate minW/minH
  27610. rect = {};
  27611. rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
  27612. rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
  27613. rect.contentW = rect.minW - contLayoutRect.deltaW;
  27614. rect.contentH = rect.minH - contLayoutRect.deltaH;
  27615. rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
  27616. rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
  27617. rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
  27618. rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
  27619. // Resize container container if minSize was changed
  27620. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  27621. rect.w = rect.minW;
  27622. rect.h = rect.minH;
  27623. container.layoutRect(rect);
  27624. this.recalc(container);
  27625. // Forced recalc for example if items are hidden/shown
  27626. if (container._lastRect === null) {
  27627. var parentCtrl = container.parent();
  27628. if (parentCtrl) {
  27629. parentCtrl._lastRect = null;
  27630. parentCtrl.recalc();
  27631. }
  27632. }
  27633. return;
  27634. }
  27635. // Update contentW/contentH so absEnd moves correctly
  27636. if (contLayoutRect.autoResize) {
  27637. rect = container.layoutRect(rect);
  27638. rect.contentW = rect.minW - contLayoutRect.deltaW;
  27639. rect.contentH = rect.minH - contLayoutRect.deltaH;
  27640. }
  27641. var flexV;
  27642. if (settings.packV == 'start') {
  27643. flexV = 0;
  27644. } else {
  27645. flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
  27646. }
  27647. // Calculate totalFlex
  27648. var totalFlex = 0;
  27649. var flexWidths = settings.flexWidths;
  27650. if (flexWidths) {
  27651. for (x = 0; x < flexWidths.length; x++) {
  27652. totalFlex += flexWidths[x];
  27653. }
  27654. } else {
  27655. totalFlex = cols;
  27656. }
  27657. // Calculate new column widths based on flex values
  27658. var ratio = availableWidth / totalFlex;
  27659. for (x = 0; x < cols; x++) {
  27660. colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
  27661. }
  27662. // Move/resize controls
  27663. posY = contPaddingBox.top;
  27664. for (y = 0; y < rows; y++) {
  27665. posX = contPaddingBox.left;
  27666. height = rowHeights[y] + flexV;
  27667. for (x = 0; x < cols; x++) {
  27668. ctrl = items[y * cols + x];
  27669. // No more controls to render then break
  27670. if (!ctrl) {
  27671. break;
  27672. }
  27673. // Get control settings and calculate x, y
  27674. ctrlSettings = ctrl.settings;
  27675. ctrlLayoutRect = ctrl.layoutRect();
  27676. width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
  27677. ctrlLayoutRect.x = posX;
  27678. ctrlLayoutRect.y = posY;
  27679. // Align control horizontal
  27680. align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
  27681. if (align == "center") {
  27682. ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
  27683. } else if (align == "right") {
  27684. ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
  27685. } else if (align == "stretch") {
  27686. ctrlLayoutRect.w = width;
  27687. }
  27688. // Align control vertical
  27689. align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
  27690. if (align == "center") {
  27691. ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
  27692. } else if (align == "bottom") {
  27693. ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
  27694. } else if (align == "stretch") {
  27695. ctrlLayoutRect.h = height;
  27696. }
  27697. ctrl.layoutRect(ctrlLayoutRect);
  27698. posX += width + spacingH;
  27699. if (ctrl.recalc) {
  27700. ctrl.recalc();
  27701. }
  27702. }
  27703. posY += height + spacingV;
  27704. }
  27705. }
  27706. });
  27707. });
  27708. // Included from: js/tinymce/classes/ui/Iframe.js
  27709. /**
  27710. * Iframe.js
  27711. *
  27712. * Copyright, Moxiecode Systems AB
  27713. * Released under LGPL License.
  27714. *
  27715. * License: http://www.tinymce.com/license
  27716. * Contributing: http://www.tinymce.com/contributing
  27717. */
  27718. /*jshint scripturl:true */
  27719. /**
  27720. * This class creates an iframe.
  27721. *
  27722. * @setting {String} url Url to open in the iframe.
  27723. *
  27724. * @-x-less Iframe.less
  27725. * @class tinymce.ui.Iframe
  27726. * @extends tinymce.ui.Widget
  27727. */
  27728. define("tinymce/ui/Iframe", [
  27729. "tinymce/ui/Widget"
  27730. ], function(Widget) {
  27731. "use strict";
  27732. return Widget.extend({
  27733. /**
  27734. * Renders the control as a HTML string.
  27735. *
  27736. * @method renderHtml
  27737. * @return {String} HTML representing the control.
  27738. */
  27739. renderHtml: function() {
  27740. var self = this;
  27741. self.addClass('iframe');
  27742. self.canFocus = false;
  27743. return (
  27744. '<iframe id="' + self._id + '" class="' + self.classes() + '" tabindex="-1" src="' +
  27745. (self.settings.url || "javascript:\'\'") + '" frameborder="0"></iframe>'
  27746. );
  27747. },
  27748. /**
  27749. * Setter for the iframe source.
  27750. *
  27751. * @method src
  27752. * @param {String} src Source URL for iframe.
  27753. */
  27754. src: function(src) {
  27755. this.getEl().src = src;
  27756. },
  27757. /**
  27758. * Inner HTML for the iframe.
  27759. *
  27760. * @method html
  27761. * @param {String} html HTML string to set as HTML inside the iframe.
  27762. * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
  27763. * @return {tinymce.ui.Iframe} Current iframe control.
  27764. */
  27765. html: function(html, callback) {
  27766. var self = this, body = this.getEl().contentWindow.document.body;
  27767. // Wait for iframe to initialize IE 10 takes time
  27768. if (!body) {
  27769. setTimeout(function() {
  27770. self.html(html);
  27771. }, 0);
  27772. } else {
  27773. body.innerHTML = html;
  27774. if (callback) {
  27775. callback();
  27776. }
  27777. }
  27778. return this;
  27779. }
  27780. });
  27781. });
  27782. // Included from: js/tinymce/classes/ui/Label.js
  27783. /**
  27784. * Label.js
  27785. *
  27786. * Copyright, Moxiecode Systems AB
  27787. * Released under LGPL License.
  27788. *
  27789. * License: http://www.tinymce.com/license
  27790. * Contributing: http://www.tinymce.com/contributing
  27791. */
  27792. /**
  27793. * This class creates a label element. A label is a simple text control
  27794. * that can be bound to other controls.
  27795. *
  27796. * @-x-less Label.less
  27797. * @class tinymce.ui.Label
  27798. * @extends tinymce.ui.Widget
  27799. */
  27800. define("tinymce/ui/Label", [
  27801. "tinymce/ui/Widget",
  27802. "tinymce/ui/DomUtils"
  27803. ], function(Widget, DomUtils) {
  27804. "use strict";
  27805. return Widget.extend({
  27806. /**
  27807. * Constructs a instance with the specified settings.
  27808. *
  27809. * @constructor
  27810. * @param {Object} settings Name/value object with settings.
  27811. * @param {Boolean} multiline Multiline label.
  27812. */
  27813. init: function(settings) {
  27814. var self = this;
  27815. self._super(settings);
  27816. self.addClass('widget');
  27817. self.addClass('label');
  27818. self.canFocus = false;
  27819. if (settings.multiline) {
  27820. self.addClass('autoscroll');
  27821. }
  27822. if (settings.strong) {
  27823. self.addClass('strong');
  27824. }
  27825. },
  27826. /**
  27827. * Initializes the current controls layout rect.
  27828. * This will be executed by the layout managers to determine the
  27829. * default minWidth/minHeight etc.
  27830. *
  27831. * @method initLayoutRect
  27832. * @return {Object} Layout rect instance.
  27833. */
  27834. initLayoutRect: function() {
  27835. var self = this, layoutRect = self._super();
  27836. if (self.settings.multiline) {
  27837. var size = DomUtils.getSize(self.getEl());
  27838. // Check if the text fits within maxW if not then try word wrapping it
  27839. if (size.width > layoutRect.maxW) {
  27840. layoutRect.minW = layoutRect.maxW;
  27841. self.addClass('multiline');
  27842. }
  27843. self.getEl().style.width = layoutRect.minW + 'px';
  27844. layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
  27845. }
  27846. return layoutRect;
  27847. },
  27848. /**
  27849. * Repaints the control after a layout operation.
  27850. *
  27851. * @method repaint
  27852. */
  27853. repaint: function() {
  27854. var self = this;
  27855. if (!self.settings.multiline) {
  27856. self.getEl().style.lineHeight = self.layoutRect().h + 'px';
  27857. }
  27858. return self._super();
  27859. },
  27860. /**
  27861. * Sets/gets the current label text.
  27862. *
  27863. * @method text
  27864. * @param {String} [text] New label text.
  27865. * @return {String|tinymce.ui.Label} Current text or current label instance.
  27866. */
  27867. text: function(text) {
  27868. var self = this;
  27869. if (self._rendered && text) {
  27870. this.innerHtml(self.encode(text));
  27871. }
  27872. return self._super(text);
  27873. },
  27874. /**
  27875. * Renders the control as a HTML string.
  27876. *
  27877. * @method renderHtml
  27878. * @return {String} HTML representing the control.
  27879. */
  27880. renderHtml: function() {
  27881. var self = this, forId = self.settings.forId;
  27882. return (
  27883. '<label id="' + self._id + '" class="' + self.classes() + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
  27884. self.encode(self._text) +
  27885. '</label>'
  27886. );
  27887. }
  27888. });
  27889. });
  27890. // Included from: js/tinymce/classes/ui/Toolbar.js
  27891. /**
  27892. * Toolbar.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. /**
  27901. * Creates a new toolbar.
  27902. *
  27903. * @class tinymce.ui.Toolbar
  27904. * @extends tinymce.ui.Container
  27905. */
  27906. define("tinymce/ui/Toolbar", [
  27907. "tinymce/ui/Container"
  27908. ], function(Container) {
  27909. "use strict";
  27910. return Container.extend({
  27911. Defaults: {
  27912. role: 'toolbar',
  27913. layout: 'flow'
  27914. },
  27915. /**
  27916. * Constructs a instance with the specified settings.
  27917. *
  27918. * @constructor
  27919. * @param {Object} settings Name/value object with settings.
  27920. */
  27921. init: function(settings) {
  27922. var self = this;
  27923. self._super(settings);
  27924. self.addClass('toolbar');
  27925. },
  27926. /**
  27927. * Called after the control has been rendered.
  27928. *
  27929. * @method postRender
  27930. */
  27931. postRender: function() {
  27932. var self = this;
  27933. self.items().addClass('toolbar-item');
  27934. return self._super();
  27935. }
  27936. });
  27937. });
  27938. // Included from: js/tinymce/classes/ui/MenuBar.js
  27939. /**
  27940. * MenuBar.js
  27941. *
  27942. * Copyright, Moxiecode Systems AB
  27943. * Released under LGPL License.
  27944. *
  27945. * License: http://www.tinymce.com/license
  27946. * Contributing: http://www.tinymce.com/contributing
  27947. */
  27948. /**
  27949. * Creates a new menubar.
  27950. *
  27951. * @-x-less MenuBar.less
  27952. * @class tinymce.ui.MenuBar
  27953. * @extends tinymce.ui.Container
  27954. */
  27955. define("tinymce/ui/MenuBar", [
  27956. "tinymce/ui/Toolbar"
  27957. ], function(Toolbar) {
  27958. "use strict";
  27959. return Toolbar.extend({
  27960. Defaults: {
  27961. role: 'menubar',
  27962. containerCls: 'menubar',
  27963. ariaRoot: true,
  27964. defaults: {
  27965. type: 'menubutton'
  27966. }
  27967. }
  27968. });
  27969. });
  27970. // Included from: js/tinymce/classes/ui/MenuButton.js
  27971. /**
  27972. * MenuButton.js
  27973. *
  27974. * Copyright, Moxiecode Systems AB
  27975. * Released under LGPL License.
  27976. *
  27977. * License: http://www.tinymce.com/license
  27978. * Contributing: http://www.tinymce.com/contributing
  27979. */
  27980. /**
  27981. * Creates a new menu button.
  27982. *
  27983. * @-x-less MenuButton.less
  27984. * @class tinymce.ui.MenuButton
  27985. * @extends tinymce.ui.Button
  27986. */
  27987. define("tinymce/ui/MenuButton", [
  27988. "tinymce/ui/Button",
  27989. "tinymce/ui/Factory",
  27990. "tinymce/ui/MenuBar"
  27991. ], function(Button, Factory, MenuBar) {
  27992. "use strict";
  27993. // TODO: Maybe add as some global function
  27994. function isChildOf(node, parent) {
  27995. while (node) {
  27996. if (parent === node) {
  27997. return true;
  27998. }
  27999. node = node.parentNode;
  28000. }
  28001. return false;
  28002. }
  28003. var MenuButton = Button.extend({
  28004. /**
  28005. * Constructs a instance with the specified settings.
  28006. *
  28007. * @constructor
  28008. * @param {Object} settings Name/value object with settings.
  28009. */
  28010. init: function(settings) {
  28011. var self = this;
  28012. self._renderOpen = true;
  28013. self._super(settings);
  28014. self.addClass('menubtn');
  28015. if (settings.fixedWidth) {
  28016. self.addClass('fixed-width');
  28017. }
  28018. self.aria('haspopup', true);
  28019. self.hasPopup = true;
  28020. },
  28021. /**
  28022. * Shows the menu for the button.
  28023. *
  28024. * @method showMenu
  28025. */
  28026. showMenu: function() {
  28027. var self = this, settings = self.settings, menu;
  28028. if (self.menu && self.menu.visible()) {
  28029. return self.hideMenu();
  28030. }
  28031. if (!self.menu) {
  28032. menu = settings.menu || [];
  28033. // Is menu array then auto constuct menu control
  28034. if (menu.length) {
  28035. menu = {
  28036. type: 'menu',
  28037. items: menu
  28038. };
  28039. } else {
  28040. menu.type = menu.type || 'menu';
  28041. }
  28042. self.menu = Factory.create(menu).parent(self).renderTo();
  28043. self.fire('createmenu');
  28044. self.menu.reflow();
  28045. self.menu.on('cancel', function(e) {
  28046. if (e.control.parent() === self.menu) {
  28047. e.stopPropagation();
  28048. self.focus();
  28049. self.hideMenu();
  28050. }
  28051. });
  28052. // Move focus to button when a menu item is selected/clicked
  28053. self.menu.on('select', function() {
  28054. self.focus();
  28055. });
  28056. self.menu.on('show hide', function(e) {
  28057. if (e.control == self.menu) {
  28058. self.activeMenu(e.type == 'show');
  28059. }
  28060. self.aria('expanded', e.type == 'show');
  28061. }).fire('show');
  28062. }
  28063. self.menu.show();
  28064. self.menu.layoutRect({w: self.layoutRect().w});
  28065. self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
  28066. },
  28067. /**
  28068. * Hides the menu for the button.
  28069. *
  28070. * @method hideMenu
  28071. */
  28072. hideMenu: function() {
  28073. var self = this;
  28074. if (self.menu) {
  28075. self.menu.items().each(function(item) {
  28076. if (item.hideMenu) {
  28077. item.hideMenu();
  28078. }
  28079. });
  28080. self.menu.hide();
  28081. }
  28082. },
  28083. /**
  28084. * Sets the active menu state.
  28085. *
  28086. * @private
  28087. */
  28088. activeMenu: function(state) {
  28089. this.toggleClass('active', state);
  28090. },
  28091. /**
  28092. * Renders the control as a HTML string.
  28093. *
  28094. * @method renderHtml
  28095. * @return {String} HTML representing the control.
  28096. */
  28097. renderHtml: function() {
  28098. var self = this, id = self._id, prefix = self.classPrefix;
  28099. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  28100. self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
  28101. return (
  28102. '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
  28103. '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
  28104. (icon ? '<i class="' + icon + '"></i>' : '') +
  28105. '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
  28106. ' <i class="' + prefix + 'caret"></i>' +
  28107. '</button>' +
  28108. '</div>'
  28109. );
  28110. },
  28111. /**
  28112. * Gets invoked after the control has been rendered.
  28113. *
  28114. * @method postRender
  28115. */
  28116. postRender: function() {
  28117. var self = this;
  28118. self.on('click', function(e) {
  28119. if (e.control === self && isChildOf(e.target, self.getEl())) {
  28120. self.showMenu();
  28121. if (e.aria) {
  28122. self.menu.items()[0].focus();
  28123. }
  28124. }
  28125. });
  28126. self.on('mouseenter', function(e) {
  28127. var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
  28128. if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
  28129. parent.items().filter('MenuButton').each(function(ctrl) {
  28130. if (ctrl.hideMenu && ctrl != overCtrl) {
  28131. if (ctrl.menu && ctrl.menu.visible()) {
  28132. hasVisibleSiblingMenu = true;
  28133. }
  28134. ctrl.hideMenu();
  28135. }
  28136. });
  28137. if (hasVisibleSiblingMenu) {
  28138. overCtrl.focus(); // Fix for: #5887
  28139. overCtrl.showMenu();
  28140. }
  28141. }
  28142. });
  28143. return self._super();
  28144. },
  28145. /**
  28146. * Sets/gets the current button text.
  28147. *
  28148. * @method text
  28149. * @param {String} [text] New button text.
  28150. * @return {String|tinymce.ui.MenuButton} Current text or current MenuButton instance.
  28151. */
  28152. text: function(text) {
  28153. var self = this, i, children;
  28154. if (self._rendered) {
  28155. children = self.getEl('open').getElementsByTagName('span');
  28156. for (i = 0; i < children.length; i++) {
  28157. children[i].innerHTML = (self.settings.icon && text ? '\u00a0' : '') + self.encode(text);
  28158. }
  28159. }
  28160. return this._super(text);
  28161. },
  28162. /**
  28163. * Removes the control and it's menus.
  28164. *
  28165. * @method remove
  28166. */
  28167. remove: function() {
  28168. this._super();
  28169. if (this.menu) {
  28170. this.menu.remove();
  28171. }
  28172. }
  28173. });
  28174. return MenuButton;
  28175. });
  28176. // Included from: js/tinymce/classes/ui/ListBox.js
  28177. /**
  28178. * ListBox.js
  28179. *
  28180. * Copyright, Moxiecode Systems AB
  28181. * Released under LGPL License.
  28182. *
  28183. * License: http://www.tinymce.com/license
  28184. * Contributing: http://www.tinymce.com/contributing
  28185. */
  28186. /**
  28187. * Creates a new list box control.
  28188. *
  28189. * @-x-less ListBox.less
  28190. * @class tinymce.ui.ListBox
  28191. * @extends tinymce.ui.MenuButton
  28192. */
  28193. define("tinymce/ui/ListBox", [
  28194. "tinymce/ui/MenuButton"
  28195. ], function(MenuButton) {
  28196. "use strict";
  28197. return MenuButton.extend({
  28198. /**
  28199. * Constructs a instance with the specified settings.
  28200. *
  28201. * @constructor
  28202. * @param {Object} settings Name/value object with settings.
  28203. * @setting {Array} values Array with values to add to list box.
  28204. */
  28205. init: function(settings) {
  28206. var self = this, values, i, selected, selectedText, lastItemCtrl;
  28207. self._values = values = settings.values;
  28208. if (values) {
  28209. for (i = 0; i < values.length; i++) {
  28210. selected = values[i].selected || settings.value === values[i].value;
  28211. if (selected) {
  28212. selectedText = selectedText || values[i].text;
  28213. self._value = values[i].value;
  28214. break;
  28215. }
  28216. }
  28217. // Default with first item
  28218. if (!selected && values.length > 0) {
  28219. selectedText = values[0].text;
  28220. self._value = values[0].value;
  28221. }
  28222. settings.menu = values;
  28223. }
  28224. settings.text = settings.text || selectedText || values[0].text;
  28225. self._super(settings);
  28226. self.addClass('listbox');
  28227. self.on('select', function(e) {
  28228. var ctrl = e.control;
  28229. if (lastItemCtrl) {
  28230. e.lastControl = lastItemCtrl;
  28231. }
  28232. if (settings.multiple) {
  28233. ctrl.active(!ctrl.active());
  28234. } else {
  28235. self.value(e.control.settings.value);
  28236. }
  28237. lastItemCtrl = ctrl;
  28238. });
  28239. },
  28240. /**
  28241. * Getter/setter function for the control value.
  28242. *
  28243. * @method value
  28244. * @param {String} [value] Value to be set.
  28245. * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
  28246. */
  28247. value: function(value) {
  28248. var self = this, active, selectedText, menu, i;
  28249. function activateByValue(menu, value) {
  28250. menu.items().each(function(ctrl) {
  28251. active = ctrl.value() === value;
  28252. if (active) {
  28253. selectedText = selectedText || ctrl.text();
  28254. }
  28255. ctrl.active(active);
  28256. if (ctrl.menu) {
  28257. activateByValue(ctrl.menu, value);
  28258. }
  28259. });
  28260. }
  28261. if (typeof(value) != "undefined") {
  28262. if (self.menu) {
  28263. activateByValue(self.menu, value);
  28264. } else {
  28265. menu = self.settings.menu;
  28266. for (i = 0; i < menu.length; i++) {
  28267. active = menu[i].value == value;
  28268. if (active) {
  28269. selectedText = selectedText || menu[i].text;
  28270. }
  28271. menu[i].active = active;
  28272. }
  28273. }
  28274. self.text(selectedText || this.settings.text);
  28275. }
  28276. return self._super(value);
  28277. }
  28278. });
  28279. });
  28280. // Included from: js/tinymce/classes/ui/MenuItem.js
  28281. /**
  28282. * MenuItem.js
  28283. *
  28284. * Copyright, Moxiecode Systems AB
  28285. * Released under LGPL License.
  28286. *
  28287. * License: http://www.tinymce.com/license
  28288. * Contributing: http://www.tinymce.com/contributing
  28289. */
  28290. /**
  28291. * Creates a new menu item.
  28292. *
  28293. * @-x-less MenuItem.less
  28294. * @class tinymce.ui.MenuItem
  28295. * @extends tinymce.ui.Control
  28296. */
  28297. define("tinymce/ui/MenuItem", [
  28298. "tinymce/ui/Widget",
  28299. "tinymce/ui/Factory",
  28300. "tinymce/Env"
  28301. ], function(Widget, Factory, Env) {
  28302. "use strict";
  28303. return Widget.extend({
  28304. Defaults: {
  28305. border: 0,
  28306. role: 'menuitem'
  28307. },
  28308. /**
  28309. * Constructs a instance with the specified settings.
  28310. *
  28311. * @constructor
  28312. * @param {Object} settings Name/value object with settings.
  28313. * @setting {Boolean} selectable Selectable menu.
  28314. * @setting {Array} menu Submenu array with items.
  28315. * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
  28316. */
  28317. init: function(settings) {
  28318. var self = this;
  28319. self.hasPopup = true;
  28320. self._super(settings);
  28321. settings = self.settings;
  28322. self.addClass('menu-item');
  28323. if (settings.menu) {
  28324. self.addClass('menu-item-expand');
  28325. }
  28326. if (settings.preview) {
  28327. self.addClass('menu-item-preview');
  28328. }
  28329. if (self._text === '-' || self._text === '|') {
  28330. self.addClass('menu-item-sep');
  28331. self.aria('role', 'separator');
  28332. self._text = '-';
  28333. }
  28334. if (settings.selectable) {
  28335. self.aria('role', 'menuitemcheckbox');
  28336. self.addClass('menu-item-checkbox');
  28337. settings.icon = 'selected';
  28338. }
  28339. if (!settings.preview && !settings.selectable) {
  28340. self.addClass('menu-item-normal');
  28341. }
  28342. self.on('mousedown', function(e) {
  28343. e.preventDefault();
  28344. });
  28345. if (settings.menu && !settings.ariaHideMenu) {
  28346. self.aria('haspopup', true);
  28347. }
  28348. },
  28349. /**
  28350. * Returns true/false if the menuitem has sub menu.
  28351. *
  28352. * @method hasMenus
  28353. * @return {Boolean} True/false state if it has submenu.
  28354. */
  28355. hasMenus: function() {
  28356. return !!this.settings.menu;
  28357. },
  28358. /**
  28359. * Shows the menu for the menu item.
  28360. *
  28361. * @method showMenu
  28362. */
  28363. showMenu: function() {
  28364. var self = this, settings = self.settings, menu, parent = self.parent();
  28365. parent.items().each(function(ctrl) {
  28366. if (ctrl !== self) {
  28367. ctrl.hideMenu();
  28368. }
  28369. });
  28370. if (settings.menu) {
  28371. menu = self.menu;
  28372. if (!menu) {
  28373. menu = settings.menu;
  28374. // Is menu array then auto constuct menu control
  28375. if (menu.length) {
  28376. menu = {
  28377. type: 'menu',
  28378. items: menu
  28379. };
  28380. } else {
  28381. menu.type = menu.type || 'menu';
  28382. }
  28383. if (parent.settings.itemDefaults) {
  28384. menu.itemDefaults = parent.settings.itemDefaults;
  28385. }
  28386. menu = self.menu = Factory.create(menu).parent(self).renderTo();
  28387. menu.reflow();
  28388. menu.fire('show');
  28389. menu.on('cancel', function(e) {
  28390. e.stopPropagation();
  28391. self.focus();
  28392. menu.hide();
  28393. });
  28394. menu.on('hide', function(e) {
  28395. if (e.control === menu) {
  28396. self.removeClass('selected');
  28397. }
  28398. });
  28399. menu.submenu = true;
  28400. } else {
  28401. menu.show();
  28402. }
  28403. menu._parentMenu = parent;
  28404. menu.addClass('menu-sub');
  28405. var rel = menu.testMoveRel(
  28406. self.getEl(),
  28407. self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
  28408. );
  28409. menu.moveRel(self.getEl(), rel);
  28410. menu.rel = rel;
  28411. rel = 'menu-sub-' + rel;
  28412. menu.removeClass(menu._lastRel);
  28413. menu.addClass(rel);
  28414. menu._lastRel = rel;
  28415. self.addClass('selected');
  28416. self.aria('expanded', true);
  28417. }
  28418. },
  28419. /**
  28420. * Hides the menu for the menu item.
  28421. *
  28422. * @method hideMenu
  28423. */
  28424. hideMenu: function() {
  28425. var self = this;
  28426. if (self.menu) {
  28427. self.menu.items().each(function(item) {
  28428. if (item.hideMenu) {
  28429. item.hideMenu();
  28430. }
  28431. });
  28432. self.menu.hide();
  28433. self.aria('expanded', false);
  28434. }
  28435. return self;
  28436. },
  28437. /**
  28438. * Renders the control as a HTML string.
  28439. *
  28440. * @method renderHtml
  28441. * @return {String} HTML representing the control.
  28442. */
  28443. renderHtml: function() {
  28444. var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self._text);
  28445. var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
  28446. if (icon) {
  28447. self.parent().addClass('menu-has-icons');
  28448. }
  28449. if (settings.image) {
  28450. icon = 'none';
  28451. image = ' style="background-image: url(\'' + settings.image + '\')"';
  28452. }
  28453. if (shortcut && Env.mac) {
  28454. // format shortcut for Mac
  28455. shortcut = shortcut.replace(/ctrl\+alt\+/i, '&#x2325;&#x2318;'); // ctrl+cmd
  28456. shortcut = shortcut.replace(/ctrl\+/i, '&#x2318;'); // ctrl symbol
  28457. shortcut = shortcut.replace(/alt\+/i, '&#x2325;'); // cmd symbol
  28458. shortcut = shortcut.replace(/shift\+/i, '&#x21E7;'); // shift symbol
  28459. }
  28460. icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
  28461. return (
  28462. '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
  28463. (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
  28464. (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
  28465. (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
  28466. (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
  28467. '</div>'
  28468. );
  28469. },
  28470. /**
  28471. * Gets invoked after the control has been rendered.
  28472. *
  28473. * @method postRender
  28474. */
  28475. postRender: function() {
  28476. var self = this, settings = self.settings;
  28477. var textStyle = settings.textStyle;
  28478. if (typeof(textStyle) == "function") {
  28479. textStyle = textStyle.call(this);
  28480. }
  28481. if (textStyle) {
  28482. var textElm = self.getEl('text');
  28483. if (textElm) {
  28484. textElm.setAttribute('style', textStyle);
  28485. }
  28486. }
  28487. self.on('mouseenter click', function(e) {
  28488. if (e.control === self) {
  28489. if (!settings.menu && e.type === 'click') {
  28490. self.fire('select');
  28491. self.parent().hideAll();
  28492. } else {
  28493. self.showMenu();
  28494. if (e.aria) {
  28495. self.menu.focus(true);
  28496. }
  28497. }
  28498. }
  28499. });
  28500. self._super();
  28501. return self;
  28502. },
  28503. active: function(state) {
  28504. if (typeof(state) != "undefined") {
  28505. this.aria('checked', state);
  28506. }
  28507. return this._super(state);
  28508. },
  28509. /**
  28510. * Removes the control and it's menus.
  28511. *
  28512. * @method remove
  28513. */
  28514. remove: function() {
  28515. this._super();
  28516. if (this.menu) {
  28517. this.menu.remove();
  28518. }
  28519. }
  28520. });
  28521. });
  28522. // Included from: js/tinymce/classes/ui/Menu.js
  28523. /**
  28524. * Menu.js
  28525. *
  28526. * Copyright, Moxiecode Systems AB
  28527. * Released under LGPL License.
  28528. *
  28529. * License: http://www.tinymce.com/license
  28530. * Contributing: http://www.tinymce.com/contributing
  28531. */
  28532. /**
  28533. * Creates a new menu.
  28534. *
  28535. * @-x-less Menu.less
  28536. * @class tinymce.ui.Menu
  28537. * @extends tinymce.ui.FloatPanel
  28538. */
  28539. define("tinymce/ui/Menu", [
  28540. "tinymce/ui/FloatPanel",
  28541. "tinymce/ui/MenuItem",
  28542. "tinymce/util/Tools"
  28543. ], function(FloatPanel, MenuItem, Tools) {
  28544. "use strict";
  28545. var Menu = FloatPanel.extend({
  28546. Defaults: {
  28547. defaultType: 'menuitem',
  28548. border: 1,
  28549. layout: 'stack',
  28550. role: 'application',
  28551. bodyRole: 'menu',
  28552. ariaRoot: true
  28553. },
  28554. /**
  28555. * Constructs a instance with the specified settings.
  28556. *
  28557. * @constructor
  28558. * @param {Object} settings Name/value object with settings.
  28559. */
  28560. init: function(settings) {
  28561. var self = this;
  28562. settings.autohide = true;
  28563. settings.constrainToViewport = true;
  28564. if (settings.itemDefaults) {
  28565. var items = settings.items, i = items.length;
  28566. while (i--) {
  28567. items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
  28568. }
  28569. }
  28570. self._super(settings);
  28571. self.addClass('menu');
  28572. },
  28573. /**
  28574. * Repaints the control after a layout operation.
  28575. *
  28576. * @method repaint
  28577. */
  28578. repaint: function() {
  28579. this.toggleClass('menu-align', true);
  28580. this._super();
  28581. this.getEl().style.height = '';
  28582. this.getEl('body').style.height = '';
  28583. return this;
  28584. },
  28585. /**
  28586. * Hides/closes the menu.
  28587. *
  28588. * @method cancel
  28589. */
  28590. cancel: function() {
  28591. var self = this;
  28592. self.hideAll();
  28593. self.fire('select');
  28594. },
  28595. /**
  28596. * Hide menu and all sub menus.
  28597. *
  28598. * @method hideAll
  28599. */
  28600. hideAll: function() {
  28601. var self = this;
  28602. this.find('menuitem').exec('hideMenu');
  28603. return self._super();
  28604. },
  28605. /*
  28606. getContainerElm: function() {
  28607. var doc = document, id = this.classPrefix + 'menucontainer';
  28608. var elm = doc.getElementById(id);
  28609. if (!elm) {
  28610. elm = doc.createElement('div');
  28611. elm.id = id;
  28612. elm.setAttribute('role', 'application');
  28613. elm.className = this.classPrefix + '-reset';
  28614. elm.style.position = 'absolute';
  28615. elm.style.top = elm.style.left = '0';
  28616. elm.style.overflow = 'visible';
  28617. doc.body.appendChild(elm);
  28618. }
  28619. return elm;
  28620. },
  28621. */
  28622. /**
  28623. * Invoked before the menu is rendered.
  28624. *
  28625. * @method preRender
  28626. */
  28627. preRender: function() {
  28628. var self = this;
  28629. self.items().each(function(ctrl) {
  28630. var settings = ctrl.settings;
  28631. if (settings.icon || settings.selectable) {
  28632. self._hasIcons = true;
  28633. return false;
  28634. }
  28635. });
  28636. return self._super();
  28637. }
  28638. });
  28639. return Menu;
  28640. });
  28641. // Included from: js/tinymce/classes/ui/Radio.js
  28642. /**
  28643. * Radio.js
  28644. *
  28645. * Copyright, Moxiecode Systems AB
  28646. * Released under LGPL License.
  28647. *
  28648. * License: http://www.tinymce.com/license
  28649. * Contributing: http://www.tinymce.com/contributing
  28650. */
  28651. /**
  28652. * Creates a new radio button.
  28653. *
  28654. * @-x-less Radio.less
  28655. * @class tinymce.ui.Radio
  28656. * @extends tinymce.ui.Checkbox
  28657. */
  28658. define("tinymce/ui/Radio", [
  28659. "tinymce/ui/Checkbox"
  28660. ], function(Checkbox) {
  28661. "use strict";
  28662. return Checkbox.extend({
  28663. Defaults: {
  28664. classes: "radio",
  28665. role: "radio"
  28666. }
  28667. });
  28668. });
  28669. // Included from: js/tinymce/classes/ui/ResizeHandle.js
  28670. /**
  28671. * ResizeHandle.js
  28672. *
  28673. * Copyright, Moxiecode Systems AB
  28674. * Released under LGPL License.
  28675. *
  28676. * License: http://www.tinymce.com/license
  28677. * Contributing: http://www.tinymce.com/contributing
  28678. */
  28679. /**
  28680. * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
  28681. *
  28682. * @-x-less ResizeHandle.less
  28683. * @class tinymce.ui.ResizeHandle
  28684. * @extends tinymce.ui.Widget
  28685. */
  28686. define("tinymce/ui/ResizeHandle", [
  28687. "tinymce/ui/Widget",
  28688. "tinymce/ui/DragHelper"
  28689. ], function(Widget, DragHelper) {
  28690. "use strict";
  28691. return Widget.extend({
  28692. /**
  28693. * Renders the control as a HTML string.
  28694. *
  28695. * @method renderHtml
  28696. * @return {String} HTML representing the control.
  28697. */
  28698. renderHtml: function() {
  28699. var self = this, prefix = self.classPrefix;
  28700. self.addClass('resizehandle');
  28701. if (self.settings.direction == "both") {
  28702. self.addClass('resizehandle-both');
  28703. }
  28704. self.canFocus = false;
  28705. return (
  28706. '<div id="' + self._id + '" class="' + self.classes() + '">' +
  28707. '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
  28708. '</div>'
  28709. );
  28710. },
  28711. /**
  28712. * Called after the control has been rendered.
  28713. *
  28714. * @method postRender
  28715. */
  28716. postRender: function() {
  28717. var self = this;
  28718. self._super();
  28719. self.resizeDragHelper = new DragHelper(this._id, {
  28720. start: function() {
  28721. self.fire('ResizeStart');
  28722. },
  28723. drag: function(e) {
  28724. if (self.settings.direction != "both") {
  28725. e.deltaX = 0;
  28726. }
  28727. self.fire('Resize', e);
  28728. },
  28729. stop: function() {
  28730. self.fire('ResizeEnd');
  28731. }
  28732. });
  28733. },
  28734. remove: function() {
  28735. if (this.resizeDragHelper) {
  28736. this.resizeDragHelper.destroy();
  28737. }
  28738. return this._super();
  28739. }
  28740. });
  28741. });
  28742. // Included from: js/tinymce/classes/ui/Spacer.js
  28743. /**
  28744. * Spacer.js
  28745. *
  28746. * Copyright, Moxiecode Systems AB
  28747. * Released under LGPL License.
  28748. *
  28749. * License: http://www.tinymce.com/license
  28750. * Contributing: http://www.tinymce.com/contributing
  28751. */
  28752. /**
  28753. * Creates a spacer. This control is used in flex layouts for example.
  28754. *
  28755. * @-x-less Spacer.less
  28756. * @class tinymce.ui.Spacer
  28757. * @extends tinymce.ui.Widget
  28758. */
  28759. define("tinymce/ui/Spacer", [
  28760. "tinymce/ui/Widget"
  28761. ], function(Widget) {
  28762. "use strict";
  28763. return Widget.extend({
  28764. /**
  28765. * Renders the control as a HTML string.
  28766. *
  28767. * @method renderHtml
  28768. * @return {String} HTML representing the control.
  28769. */
  28770. renderHtml: function() {
  28771. var self = this;
  28772. self.addClass('spacer');
  28773. self.canFocus = false;
  28774. return '<div id="' + self._id + '" class="' + self.classes() + '"></div>';
  28775. }
  28776. });
  28777. });
  28778. // Included from: js/tinymce/classes/ui/SplitButton.js
  28779. /**
  28780. * SplitButton.js
  28781. *
  28782. * Copyright, Moxiecode Systems AB
  28783. * Released under LGPL License.
  28784. *
  28785. * License: http://www.tinymce.com/license
  28786. * Contributing: http://www.tinymce.com/contributing
  28787. */
  28788. /**
  28789. * Creates a split button.
  28790. *
  28791. * @-x-less SplitButton.less
  28792. * @class tinymce.ui.SplitButton
  28793. * @extends tinymce.ui.Button
  28794. */
  28795. define("tinymce/ui/SplitButton", [
  28796. "tinymce/ui/MenuButton",
  28797. "tinymce/ui/DomUtils"
  28798. ], function(MenuButton, DomUtils) {
  28799. return MenuButton.extend({
  28800. Defaults: {
  28801. classes: "widget btn splitbtn",
  28802. role: "button"
  28803. },
  28804. /**
  28805. * Repaints the control after a layout operation.
  28806. *
  28807. * @method repaint
  28808. */
  28809. repaint: function() {
  28810. var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
  28811. self._super();
  28812. mainButtonElm = elm.firstChild;
  28813. menuButtonElm = elm.lastChild;
  28814. DomUtils.css(mainButtonElm, {
  28815. width: rect.w - DomUtils.getSize(menuButtonElm).width,
  28816. height: rect.h - 2
  28817. });
  28818. DomUtils.css(menuButtonElm, {
  28819. height: rect.h - 2
  28820. });
  28821. return self;
  28822. },
  28823. /**
  28824. * Sets the active menu state.
  28825. *
  28826. * @private
  28827. */
  28828. activeMenu: function(state) {
  28829. var self = this;
  28830. DomUtils.toggleClass(self.getEl().lastChild, self.classPrefix + 'active', state);
  28831. },
  28832. /**
  28833. * Renders the control as a HTML string.
  28834. *
  28835. * @method renderHtml
  28836. * @return {String} HTML representing the control.
  28837. */
  28838. renderHtml: function() {
  28839. var self = this, id = self._id, prefix = self.classPrefix;
  28840. var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
  28841. return (
  28842. '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
  28843. '<button type="button" hidefocus="1" tabindex="-1">' +
  28844. (icon ? '<i class="' + icon + '"></i>' : '') +
  28845. (self._text ? (icon ? ' ' : '') + self._text : '') +
  28846. '</button>' +
  28847. '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
  28848. //(icon ? '<i class="' + icon + '"></i>' : '') +
  28849. (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
  28850. ' <i class="' + prefix + 'caret"></i>' +
  28851. '</button>' +
  28852. '</div>'
  28853. );
  28854. },
  28855. /**
  28856. * Called after the control has been rendered.
  28857. *
  28858. * @method postRender
  28859. */
  28860. postRender: function() {
  28861. var self = this, onClickHandler = self.settings.onclick;
  28862. self.on('click', function(e) {
  28863. var node = e.target;
  28864. if (e.control == this) {
  28865. // Find clicks that is on the main button
  28866. while (node) {
  28867. if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
  28868. e.stopImmediatePropagation();
  28869. onClickHandler.call(this, e);
  28870. return;
  28871. }
  28872. node = node.parentNode;
  28873. }
  28874. }
  28875. });
  28876. delete self.settings.onclick;
  28877. return self._super();
  28878. }
  28879. });
  28880. });
  28881. // Included from: js/tinymce/classes/ui/StackLayout.js
  28882. /**
  28883. * StackLayout.js
  28884. *
  28885. * Copyright, Moxiecode Systems AB
  28886. * Released under LGPL License.
  28887. *
  28888. * License: http://www.tinymce.com/license
  28889. * Contributing: http://www.tinymce.com/contributing
  28890. */
  28891. /**
  28892. * This layout uses the browsers layout when the items are blocks.
  28893. *
  28894. * @-x-less StackLayout.less
  28895. * @class tinymce.ui.StackLayout
  28896. * @extends tinymce.ui.FlowLayout
  28897. */
  28898. define("tinymce/ui/StackLayout", [
  28899. "tinymce/ui/FlowLayout"
  28900. ], function(FlowLayout) {
  28901. "use strict";
  28902. return FlowLayout.extend({
  28903. Defaults: {
  28904. containerClass: 'stack-layout',
  28905. controlClass: 'stack-layout-item',
  28906. endClass : 'break'
  28907. }
  28908. });
  28909. });
  28910. // Included from: js/tinymce/classes/ui/TabPanel.js
  28911. /**
  28912. * TabPanel.js
  28913. *
  28914. * Copyright, Moxiecode Systems AB
  28915. * Released under LGPL License.
  28916. *
  28917. * License: http://www.tinymce.com/license
  28918. * Contributing: http://www.tinymce.com/contributing
  28919. */
  28920. /**
  28921. * Creates a tab panel control.
  28922. *
  28923. * @-x-less TabPanel.less
  28924. * @class tinymce.ui.TabPanel
  28925. * @extends tinymce.ui.Panel
  28926. *
  28927. * @setting {Number} activeTab Active tab index.
  28928. */
  28929. define("tinymce/ui/TabPanel", [
  28930. "tinymce/ui/Panel",
  28931. "tinymce/ui/DomUtils"
  28932. ], function(Panel, DomUtils) {
  28933. "use strict";
  28934. return Panel.extend({
  28935. lastIdx: 0,
  28936. Defaults: {
  28937. layout: 'absolute',
  28938. defaults: {
  28939. type: 'panel'
  28940. }
  28941. },
  28942. /**
  28943. * Activates the specified tab by index.
  28944. *
  28945. * @method activateTab
  28946. * @param {Number} idx Index of the tab to activate.
  28947. */
  28948. activateTab: function(idx) {
  28949. var activeTabElm;
  28950. if (this.activeTabId) {
  28951. activeTabElm = this.getEl(this.activeTabId);
  28952. DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
  28953. activeTabElm.setAttribute('aria-selected', "false");
  28954. }
  28955. this.activeTabId = 't' + idx;
  28956. activeTabElm = this.getEl('t' + idx);
  28957. activeTabElm.setAttribute('aria-selected', "true");
  28958. DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
  28959. if (idx != this.lastIdx) {
  28960. this.items()[this.lastIdx].hide();
  28961. this.lastIdx = idx;
  28962. }
  28963. this.items()[idx].show().fire('showtab');
  28964. this.reflow();
  28965. },
  28966. /**
  28967. * Renders the control as a HTML string.
  28968. *
  28969. * @method renderHtml
  28970. * @return {String} HTML representing the control.
  28971. */
  28972. renderHtml: function() {
  28973. var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
  28974. self.preRender();
  28975. layout.preRender(self);
  28976. self.items().each(function(ctrl, i) {
  28977. var id = self._id + '-t' + i;
  28978. ctrl.aria('role', 'tabpanel');
  28979. ctrl.aria('labelledby', id);
  28980. tabsHtml += (
  28981. '<div id="' + id + '" class="' + prefix + 'tab" ' +
  28982. 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
  28983. self.encode(ctrl.settings.title) +
  28984. '</div>'
  28985. );
  28986. });
  28987. return (
  28988. '<div id="' + self._id + '" class="' + self.classes() + '" hidefocus="1" tabindex="-1">' +
  28989. '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
  28990. tabsHtml +
  28991. '</div>' +
  28992. '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
  28993. layout.renderHtml(self) +
  28994. '</div>' +
  28995. '</div>'
  28996. );
  28997. },
  28998. /**
  28999. * Called after the control has been rendered.
  29000. *
  29001. * @method postRender
  29002. */
  29003. postRender: function() {
  29004. var self = this;
  29005. self._super();
  29006. self.settings.activeTab = self.settings.activeTab || 0;
  29007. self.activateTab(self.settings.activeTab);
  29008. this.on('click', function(e) {
  29009. var targetParent = e.target.parentNode;
  29010. if (e.target.parentNode.id == self._id + '-head') {
  29011. var i = targetParent.childNodes.length;
  29012. while (i--) {
  29013. if (targetParent.childNodes[i] == e.target) {
  29014. self.activateTab(i);
  29015. }
  29016. }
  29017. }
  29018. });
  29019. },
  29020. /**
  29021. * Initializes the current controls layout rect.
  29022. * This will be executed by the layout managers to determine the
  29023. * default minWidth/minHeight etc.
  29024. *
  29025. * @method initLayoutRect
  29026. * @return {Object} Layout rect instance.
  29027. */
  29028. initLayoutRect: function() {
  29029. var self = this, rect, minW, minH;
  29030. minW = DomUtils.getSize(self.getEl('head')).width;
  29031. minW = minW < 0 ? 0 : minW;
  29032. minH = 0;
  29033. self.items().each(function(item, i) {
  29034. minW = Math.max(minW, item.layoutRect().minW);
  29035. minH = Math.max(minH, item.layoutRect().minH);
  29036. if (self.settings.activeTab != i) {
  29037. item.hide();
  29038. }
  29039. });
  29040. self.items().each(function(ctrl) {
  29041. ctrl.settings.x = 0;
  29042. ctrl.settings.y = 0;
  29043. ctrl.settings.w = minW;
  29044. ctrl.settings.h = minH;
  29045. ctrl.layoutRect({
  29046. x: 0,
  29047. y: 0,
  29048. w: minW,
  29049. h: minH
  29050. });
  29051. });
  29052. var headH = DomUtils.getSize(self.getEl('head')).height;
  29053. self.settings.minWidth = minW;
  29054. self.settings.minHeight = minH + headH;
  29055. rect = self._super();
  29056. rect.deltaH += headH;
  29057. rect.innerH = rect.h - rect.deltaH;
  29058. return rect;
  29059. }
  29060. });
  29061. });
  29062. // Included from: js/tinymce/classes/ui/TextBox.js
  29063. /**
  29064. * TextBox.js
  29065. *
  29066. * Copyright, Moxiecode Systems AB
  29067. * Released under LGPL License.
  29068. *
  29069. * License: http://www.tinymce.com/license
  29070. * Contributing: http://www.tinymce.com/contributing
  29071. */
  29072. /**
  29073. * Creates a new textbox.
  29074. *
  29075. * @-x-less TextBox.less
  29076. * @class tinymce.ui.TextBox
  29077. * @extends tinymce.ui.Widget
  29078. */
  29079. define("tinymce/ui/TextBox", [
  29080. "tinymce/ui/Widget",
  29081. "tinymce/ui/DomUtils"
  29082. ], function(Widget, DomUtils) {
  29083. "use strict";
  29084. return Widget.extend({
  29085. /**
  29086. * Constructs a instance with the specified settings.
  29087. *
  29088. * @constructor
  29089. * @param {Object} settings Name/value object with settings.
  29090. * @setting {Boolean} multiline True if the textbox is a multiline control.
  29091. * @setting {Number} maxLength Max length for the textbox.
  29092. * @setting {Number} size Size of the textbox in characters.
  29093. */
  29094. init: function(settings) {
  29095. var self = this;
  29096. self._super(settings);
  29097. self._value = settings.value || '';
  29098. self.addClass('textbox');
  29099. if (settings.multiline) {
  29100. self.addClass('multiline');
  29101. } else {
  29102. // TODO: Rework this
  29103. self.on('keydown', function(e) {
  29104. if (e.keyCode == 13) {
  29105. self.parents().reverse().each(function(ctrl) {
  29106. e.preventDefault();
  29107. if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
  29108. ctrl.fire('submit', {data: ctrl.toJSON()});
  29109. return false;
  29110. }
  29111. });
  29112. }
  29113. });
  29114. }
  29115. },
  29116. /**
  29117. * Getter/setter function for the disabled state.
  29118. *
  29119. * @method value
  29120. * @param {Boolean} [state] State to be set.
  29121. * @return {Boolean|tinymce.ui.ComboBox} True/false or self if it's a set operation.
  29122. */
  29123. disabled: function(state) {
  29124. var self = this;
  29125. if (self._rendered && typeof(state) != 'undefined') {
  29126. self.getEl().disabled = state;
  29127. }
  29128. return self._super(state);
  29129. },
  29130. /**
  29131. * Getter/setter function for the control value.
  29132. *
  29133. * @method value
  29134. * @param {String} [value] Value to be set.
  29135. * @return {String|tinymce.ui.ComboBox} Value or self if it's a set operation.
  29136. */
  29137. value: function(value) {
  29138. var self = this;
  29139. if (typeof(value) != "undefined") {
  29140. self._value = value;
  29141. if (self._rendered) {
  29142. self.getEl().value = value;
  29143. }
  29144. return self;
  29145. }
  29146. if (self._rendered) {
  29147. return self.getEl().value;
  29148. }
  29149. return self._value;
  29150. },
  29151. /**
  29152. * Repaints the control after a layout operation.
  29153. *
  29154. * @method repaint
  29155. */
  29156. repaint: function() {
  29157. var self = this, style, rect, borderBox, borderW = 0, borderH = 0, lastRepaintRect;
  29158. style = self.getEl().style;
  29159. rect = self._layoutRect;
  29160. lastRepaintRect = self._lastRepaintRect || {};
  29161. // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
  29162. var doc = document;
  29163. if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
  29164. style.lineHeight = (rect.h - borderH) + 'px';
  29165. }
  29166. borderBox = self._borderBox;
  29167. borderW = borderBox.left + borderBox.right + 8;
  29168. borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
  29169. if (rect.x !== lastRepaintRect.x) {
  29170. style.left = rect.x + 'px';
  29171. lastRepaintRect.x = rect.x;
  29172. }
  29173. if (rect.y !== lastRepaintRect.y) {
  29174. style.top = rect.y + 'px';
  29175. lastRepaintRect.y = rect.y;
  29176. }
  29177. if (rect.w !== lastRepaintRect.w) {
  29178. style.width = (rect.w - borderW) + 'px';
  29179. lastRepaintRect.w = rect.w;
  29180. }
  29181. if (rect.h !== lastRepaintRect.h) {
  29182. style.height = (rect.h - borderH) + 'px';
  29183. lastRepaintRect.h = rect.h;
  29184. }
  29185. self._lastRepaintRect = lastRepaintRect;
  29186. self.fire('repaint', {}, false);
  29187. return self;
  29188. },
  29189. /**
  29190. * Renders the control as a HTML string.
  29191. *
  29192. * @method renderHtml
  29193. * @return {String} HTML representing the control.
  29194. */
  29195. renderHtml: function() {
  29196. var self = this, id = self._id, settings = self.settings, value = self.encode(self._value, false), extraAttrs = '';
  29197. if ("spellcheck" in settings) {
  29198. extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
  29199. }
  29200. if (settings.maxLength) {
  29201. extraAttrs += ' maxlength="' + settings.maxLength + '"';
  29202. }
  29203. if (settings.size) {
  29204. extraAttrs += ' size="' + settings.size + '"';
  29205. }
  29206. if (settings.subtype) {
  29207. extraAttrs += ' type="' + settings.subtype + '"';
  29208. }
  29209. if (self.disabled()) {
  29210. extraAttrs += ' disabled="disabled"';
  29211. }
  29212. if (settings.multiline) {
  29213. return (
  29214. '<textarea id="' + id + '" class="' + self.classes() + '" ' +
  29215. (settings.rows ? ' rows="' + settings.rows + '"' : '') +
  29216. ' hidefocus="1"' + extraAttrs + '>' + value +
  29217. '</textarea>'
  29218. );
  29219. }
  29220. return '<input id="' + id + '" class="' + self.classes() + '" value="' + value + '" hidefocus="1"' + extraAttrs + ' />';
  29221. },
  29222. /**
  29223. * Called after the control has been rendered.
  29224. *
  29225. * @method postRender
  29226. */
  29227. postRender: function() {
  29228. var self = this;
  29229. DomUtils.on(self.getEl(), 'change', function(e) {
  29230. self.fire('change', e);
  29231. });
  29232. return self._super();
  29233. },
  29234. remove: function() {
  29235. DomUtils.off(this.getEl());
  29236. this._super();
  29237. }
  29238. });
  29239. });
  29240. // Included from: js/tinymce/classes/ui/Throbber.js
  29241. /**
  29242. * Throbber.js
  29243. *
  29244. * Copyright, Moxiecode Systems AB
  29245. * Released under LGPL License.
  29246. *
  29247. * License: http://www.tinymce.com/license
  29248. * Contributing: http://www.tinymce.com/contributing
  29249. */
  29250. /**
  29251. * This class enables you to display a Throbber for any element.
  29252. *
  29253. * @-x-less Throbber.less
  29254. * @class tinymce.ui.Throbber
  29255. */
  29256. define("tinymce/ui/Throbber", [
  29257. "tinymce/ui/DomUtils"
  29258. ], function(DomUtils) {
  29259. "use strict";
  29260. /**
  29261. * Constructs a new throbber.
  29262. *
  29263. * @constructor
  29264. * @param {Element} elm DOM Html element to display throbber in.
  29265. */
  29266. return function(elm) {
  29267. var self = this, state;
  29268. /**
  29269. * Shows the throbber.
  29270. *
  29271. * @method show
  29272. * @param {Number} [time] Time to wait before showing.
  29273. * @return {tinymce.ui.Throbber} Current throbber instance.
  29274. */
  29275. self.show = function(time) {
  29276. self.hide();
  29277. state = true;
  29278. window.setTimeout(function() {
  29279. if (state) {
  29280. elm.appendChild(DomUtils.createFragment('<div class="mce-throbber"></div>'));
  29281. }
  29282. }, time || 0);
  29283. return self;
  29284. };
  29285. /**
  29286. * Hides the throbber.
  29287. *
  29288. * @method hide
  29289. * @return {tinymce.ui.Throbber} Current throbber instance.
  29290. */
  29291. self.hide = function() {
  29292. var child = elm.lastChild;
  29293. if (child && child.className.indexOf('throbber') != -1) {
  29294. child.parentNode.removeChild(child);
  29295. }
  29296. state = false;
  29297. return self;
  29298. };
  29299. };
  29300. });
  29301. 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"]);
  29302. })(this);