1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148281492815028151281522815328154281552815628157281582815928160281612816228163281642816528166281672816828169281702817128172281732817428175281762817728178281792818028181281822818328184281852818628187281882818928190281912819228193281942819528196281972819828199282002820128202282032820428205282062820728208282092821028211282122821328214282152821628217282182821928220282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667316683166931670316713167231673316743167531676316773167831679316803168131682316833168431685316863168731688316893169031691316923169331694316953169631697316983169931700317013170231703317043170531706317073170831709317103171131712317133171431715317163171731718317193172031721317223172331724317253172631727317283172931730317313173231733317343173531736317373173831739317403174131742317433174431745317463174731748317493175031751317523175331754317553175631757317583175931760317613176231763317643176531766317673176831769317703177131772317733177431775317763177731778317793178031781317823178331784317853178631787317883178931790317913179231793317943179531796317973179831799318003180131802318033180431805318063180731808318093181031811318123181331814318153181631817318183181931820318213182231823318243182531826318273182831829318303183131832318333183431835318363183731838318393184031841318423184331844318453184631847318483184931850318513185231853318543185531856318573185831859318603186131862318633186431865318663186731868318693187031871318723187331874318753187631877318783187931880318813188231883318843188531886318873188831889318903189131892318933189431895318963189731898318993190031901319023190331904319053190631907319083190931910319113191231913319143191531916319173191831919319203192131922319233192431925319263192731928319293193031931319323193331934319353193631937319383193931940319413194231943319443194531946319473194831949319503195131952319533195431955319563195731958319593196031961319623196331964319653196631967319683196931970319713197231973319743197531976319773197831979319803198131982319833198431985319863198731988319893199031991319923199331994319953199631997319983199932000320013200232003320043200532006320073200832009320103201132012320133201432015320163201732018320193202032021320223202332024320253202632027320283202932030320313203232033320343203532036320373203832039320403204132042320433204432045320463204732048320493205032051320523205332054320553205632057320583205932060320613206232063320643206532066320673206832069320703207132072320733207432075320763207732078320793208032081320823208332084320853208632087320883208932090320913209232093320943209532096320973209832099321003210132102321033210432105321063210732108321093211032111321123211332114321153211632117321183211932120321213212232123321243212532126321273212832129321303213132132321333213432135321363213732138321393214032141321423214332144321453214632147321483214932150321513215232153321543215532156321573215832159321603216132162321633216432165321663216732168321693217032171321723217332174321753217632177321783217932180321813218232183321843218532186321873218832189321903219132192321933219432195321963219732198321993220032201322023220332204322053220632207322083220932210322113221232213322143221532216322173221832219322203222132222322233222432225322263222732228322293223032231322323223332234322353223632237322383223932240322413224232243322443224532246322473224832249322503225132252322533225432255322563225732258322593226032261322623226332264322653226632267322683226932270322713227232273322743227532276322773227832279322803228132282322833228432285322863228732288322893229032291322923229332294322953229632297322983229932300323013230232303323043230532306323073230832309323103231132312323133231432315323163231732318323193232032321323223232332324323253232632327323283232932330323313233232333323343233532336323373233832339323403234132342323433234432345323463234732348323493235032351323523235332354323553235632357323583235932360323613236232363323643236532366323673236832369323703237132372323733237432375323763237732378323793238032381323823238332384323853238632387323883238932390323913239232393323943239532396323973239832399324003240132402324033240432405324063240732408324093241032411324123241332414324153241632417324183241932420324213242232423324243242532426324273242832429324303243132432324333243432435324363243732438324393244032441324423244332444324453244632447324483244932450324513245232453324543245532456324573245832459324603246132462324633246432465324663246732468324693247032471324723247332474324753247632477324783247932480324813248232483324843248532486324873248832489324903249132492324933249432495324963249732498324993250032501325023250332504325053250632507325083250932510325113251232513325143251532516325173251832519325203252132522325233252432525325263252732528325293253032531325323253332534325353253632537325383253932540325413254232543325443254532546325473254832549325503255132552325533255432555325563255732558325593256032561325623256332564325653256632567325683256932570325713257232573325743257532576325773257832579325803258132582325833258432585325863258732588325893259032591325923259332594325953259632597325983259932600326013260232603326043260532606326073260832609326103261132612326133261432615326163261732618326193262032621326223262332624326253262632627326283262932630326313263232633326343263532636326373263832639326403264132642326433264432645326463264732648326493265032651326523265332654326553265632657326583265932660326613266232663326643266532666326673266832669326703267132672326733267432675326763267732678326793268032681326823268332684326853268632687326883268932690326913269232693326943269532696326973269832699327003270132702327033270432705327063270732708327093271032711327123271332714327153271632717327183271932720327213272232723327243272532726327273272832729327303273132732327333273432735327363273732738327393274032741327423274332744327453274632747327483274932750327513275232753327543275532756327573275832759327603276132762327633276432765327663276732768327693277032771327723277332774327753277632777327783277932780327813278232783327843278532786327873278832789327903279132792327933279432795327963279732798327993280032801328023280332804328053280632807328083280932810328113281232813328143281532816328173281832819328203282132822328233282432825328263282732828328293283032831328323283332834328353283632837328383283932840328413284232843328443284532846328473284832849328503285132852328533285432855328563285732858328593286032861328623286332864328653286632867328683286932870328713287232873328743287532876328773287832879328803288132882328833288432885328863288732888328893289032891328923289332894328953289632897328983289932900329013290232903329043290532906329073290832909329103291132912329133291432915329163291732918329193292032921329223292332924329253292632927329283292932930329313293232933329343293532936329373293832939329403294132942329433294432945329463294732948329493295032951329523295332954329553295632957329583295932960329613296232963329643296532966329673296832969329703297132972329733297432975329763297732978329793298032981329823298332984329853298632987329883298932990329913299232993329943299532996329973299832999330003300133002330033300433005330063300733008330093301033011330123301333014330153301633017330183301933020330213302233023330243302533026330273302833029330303303133032330333303433035330363303733038330393304033041330423304333044330453304633047330483304933050330513305233053330543305533056330573305833059330603306133062330633306433065330663306733068330693307033071330723307333074330753307633077330783307933080330813308233083330843308533086330873308833089330903309133092330933309433095330963309733098330993310033101331023310333104331053310633107331083310933110331113311233113331143311533116331173311833119331203312133122331233312433125331263312733128331293313033131331323313333134331353313633137331383313933140331413314233143331443314533146331473314833149331503315133152331533315433155331563315733158331593316033161331623316333164331653316633167331683316933170331713317233173331743317533176331773317833179331803318133182331833318433185331863318733188331893319033191331923319333194331953319633197331983319933200332013320233203332043320533206332073320833209332103321133212332133321433215332163321733218332193322033221332223322333224332253322633227332283322933230332313323233233332343323533236332373323833239332403324133242332433324433245332463324733248332493325033251332523325333254332553325633257332583325933260332613326233263332643326533266332673326833269332703327133272332733327433275332763327733278332793328033281332823328333284332853328633287332883328933290332913329233293332943329533296332973329833299333003330133302333033330433305333063330733308333093331033311333123331333314333153331633317333183331933320333213332233323333243332533326333273332833329333303333133332333333333433335333363333733338333393334033341333423334333344333453334633347333483334933350333513335233353333543335533356333573335833359333603336133362333633336433365333663336733368333693337033371333723337333374333753337633377333783337933380333813338233383333843338533386333873338833389333903339133392333933339433395333963339733398333993340033401334023340333404334053340633407334083340933410334113341233413334143341533416334173341833419334203342133422334233342433425334263342733428334293343033431334323343333434334353343633437334383343933440334413344233443334443344533446334473344833449334503345133452334533345433455334563345733458334593346033461334623346333464334653346633467334683346933470334713347233473334743347533476334773347833479334803348133482334833348433485334863348733488334893349033491334923349333494334953349633497334983349933500335013350233503335043350533506335073350833509335103351133512335133351433515335163351733518335193352033521335223352333524335253352633527335283352933530335313353233533335343353533536335373353833539335403354133542335433354433545335463354733548335493355033551335523355333554335553355633557335583355933560335613356233563335643356533566335673356833569335703357133572335733357433575335763357733578335793358033581335823358333584335853358633587335883358933590335913359233593335943359533596335973359833599336003360133602336033360433605336063360733608336093361033611336123361333614336153361633617336183361933620336213362233623336243362533626336273362833629336303363133632336333363433635336363363733638336393364033641336423364333644336453364633647336483364933650336513365233653336543365533656336573365833659336603366133662336633366433665336663366733668336693367033671336723367333674336753367633677336783367933680336813368233683336843368533686336873368833689336903369133692336933369433695336963369733698336993370033701337023370333704337053370633707337083370933710337113371233713337143371533716337173371833719337203372133722337233372433725337263372733728337293373033731337323373333734337353373633737337383373933740337413374233743337443374533746337473374833749337503375133752337533375433755337563375733758337593376033761337623376333764337653376633767337683376933770337713377233773337743377533776337773377833779337803378133782337833378433785337863378733788337893379033791337923379333794337953379633797337983379933800338013380233803338043380533806338073380833809338103381133812338133381433815338163381733818338193382033821338223382333824338253382633827338283382933830338313383233833338343383533836338373383833839338403384133842338433384433845338463384733848338493385033851338523385333854338553385633857338583385933860338613386233863338643386533866338673386833869338703387133872338733387433875338763387733878338793388033881338823388333884338853388633887338883388933890338913389233893338943389533896338973389833899339003390133902339033390433905339063390733908339093391033911339123391333914339153391633917339183391933920339213392233923339243392533926339273392833929339303393133932339333393433935339363393733938339393394033941339423394333944339453394633947339483394933950339513395233953339543395533956339573395833959339603396133962339633396433965339663396733968339693397033971339723397333974339753397633977339783397933980339813398233983339843398533986339873398833989339903399133992339933399433995339963399733998339993400034001340023400334004340053400634007340083400934010340113401234013340143401534016340173401834019340203402134022340233402434025340263402734028340293403034031340323403334034340353403634037340383403934040340413404234043340443404534046340473404834049340503405134052340533405434055340563405734058340593406034061340623406334064340653406634067340683406934070340713407234073340743407534076340773407834079340803408134082340833408434085340863408734088340893409034091340923409334094340953409634097340983409934100341013410234103341043410534106341073410834109341103411134112341133411434115341163411734118341193412034121341223412334124341253412634127341283412934130341313413234133341343413534136341373413834139341403414134142341433414434145341463414734148341493415034151341523415334154341553415634157341583415934160341613416234163341643416534166341673416834169341703417134172341733417434175341763417734178341793418034181341823418334184341853418634187341883418934190341913419234193341943419534196341973419834199342003420134202342033420434205342063420734208342093421034211342123421334214342153421634217342183421934220342213422234223342243422534226342273422834229342303423134232342333423434235342363423734238342393424034241342423424334244342453424634247342483424934250342513425234253342543425534256342573425834259342603426134262342633426434265342663426734268342693427034271342723427334274342753427634277342783427934280342813428234283342843428534286342873428834289342903429134292342933429434295342963429734298342993430034301343023430334304343053430634307343083430934310343113431234313343143431534316343173431834319343203432134322343233432434325343263432734328343293433034331343323433334334343353433634337343383433934340343413434234343343443434534346343473434834349343503435134352343533435434355343563435734358343593436034361343623436334364343653436634367343683436934370343713437234373343743437534376343773437834379343803438134382343833438434385343863438734388343893439034391343923439334394343953439634397343983439934400344013440234403344043440534406344073440834409344103441134412344133441434415344163441734418344193442034421344223442334424344253442634427344283442934430344313443234433344343443534436344373443834439344403444134442344433444434445344463444734448344493445034451344523445334454344553445634457344583445934460344613446234463344643446534466344673446834469344703447134472344733447434475344763447734478344793448034481344823448334484344853448634487344883448934490344913449234493344943449534496344973449834499345003450134502345033450434505345063450734508345093451034511345123451334514345153451634517345183451934520345213452234523345243452534526345273452834529345303453134532345333453434535345363453734538345393454034541345423454334544345453454634547345483454934550345513455234553345543455534556345573455834559345603456134562345633456434565345663456734568345693457034571345723457334574345753457634577345783457934580345813458234583345843458534586345873458834589345903459134592345933459434595345963459734598345993460034601346023460334604346053460634607346083460934610346113461234613346143461534616346173461834619346203462134622346233462434625346263462734628346293463034631346323463334634346353463634637346383463934640346413464234643346443464534646346473464834649346503465134652346533465434655346563465734658346593466034661346623466334664346653466634667346683466934670346713467234673346743467534676346773467834679346803468134682346833468434685346863468734688346893469034691346923469334694346953469634697346983469934700347013470234703347043470534706347073470834709347103471134712347133471434715347163471734718347193472034721347223472334724347253472634727347283472934730347313473234733347343473534736347373473834739347403474134742347433474434745347463474734748347493475034751347523475334754347553475634757347583475934760347613476234763347643476534766347673476834769347703477134772347733477434775347763477734778347793478034781347823478334784347853478634787347883478934790347913479234793347943479534796347973479834799348003480134802348033480434805348063480734808348093481034811348123481334814348153481634817348183481934820348213482234823348243482534826348273482834829348303483134832348333483434835348363483734838348393484034841348423484334844348453484634847348483484934850348513485234853348543485534856348573485834859348603486134862348633486434865348663486734868348693487034871348723487334874348753487634877348783487934880348813488234883348843488534886348873488834889348903489134892348933489434895348963489734898348993490034901349023490334904349053490634907349083490934910349113491234913349143491534916349173491834919349203492134922349233492434925349263492734928349293493034931349323493334934349353493634937349383493934940349413494234943349443494534946349473494834949349503495134952349533495434955349563495734958349593496034961349623496334964349653496634967349683496934970349713497234973349743497534976349773497834979349803498134982349833498434985349863498734988349893499034991349923499334994349953499634997349983499935000350013500235003350043500535006350073500835009350103501135012350133501435015350163501735018350193502035021350223502335024350253502635027350283502935030350313503235033350343503535036350373503835039350403504135042350433504435045350463504735048350493505035051350523505335054350553505635057350583505935060350613506235063350643506535066350673506835069350703507135072350733507435075350763507735078350793508035081350823508335084350853508635087350883508935090350913509235093350943509535096350973509835099351003510135102351033510435105351063510735108351093511035111351123511335114351153511635117351183511935120351213512235123351243512535126351273512835129351303513135132351333513435135351363513735138351393514035141351423514335144351453514635147351483514935150351513515235153351543515535156351573515835159351603516135162351633516435165351663516735168351693517035171351723517335174351753517635177351783517935180351813518235183351843518535186351873518835189351903519135192351933519435195351963519735198351993520035201352023520335204352053520635207352083520935210352113521235213352143521535216352173521835219352203522135222352233522435225352263522735228352293523035231352323523335234352353523635237352383523935240352413524235243352443524535246352473524835249352503525135252352533525435255352563525735258352593526035261352623526335264352653526635267352683526935270352713527235273352743527535276352773527835279352803528135282352833528435285352863528735288352893529035291352923529335294352953529635297352983529935300353013530235303353043530535306353073530835309353103531135312353133531435315353163531735318353193532035321353223532335324353253532635327353283532935330353313533235333353343533535336353373533835339353403534135342353433534435345353463534735348353493535035351353523535335354353553535635357353583535935360353613536235363353643536535366353673536835369353703537135372353733537435375353763537735378353793538035381353823538335384353853538635387353883538935390353913539235393353943539535396353973539835399354003540135402354033540435405354063540735408354093541035411354123541335414354153541635417354183541935420354213542235423354243542535426354273542835429354303543135432354333543435435354363543735438354393544035441354423544335444354453544635447354483544935450354513545235453354543545535456354573545835459354603546135462354633546435465354663546735468354693547035471354723547335474354753547635477354783547935480354813548235483354843548535486354873548835489354903549135492354933549435495354963549735498354993550035501355023550335504355053550635507355083550935510355113551235513355143551535516355173551835519355203552135522355233552435525355263552735528355293553035531355323553335534355353553635537355383553935540355413554235543355443554535546355473554835549355503555135552355533555435555355563555735558355593556035561355623556335564355653556635567355683556935570355713557235573355743557535576355773557835579355803558135582355833558435585355863558735588355893559035591355923559335594355953559635597355983559935600356013560235603356043560535606356073560835609356103561135612356133561435615356163561735618356193562035621356223562335624356253562635627356283562935630356313563235633356343563535636356373563835639356403564135642356433564435645356463564735648356493565035651356523565335654356553565635657356583565935660356613566235663356643566535666356673566835669356703567135672356733567435675356763567735678356793568035681356823568335684356853568635687356883568935690356913569235693356943569535696356973569835699357003570135702357033570435705357063570735708357093571035711357123571335714357153571635717357183571935720357213572235723357243572535726357273572835729357303573135732357333573435735357363573735738357393574035741357423574335744357453574635747357483574935750357513575235753357543575535756357573575835759357603576135762357633576435765357663576735768357693577035771357723577335774357753577635777357783577935780357813578235783357843578535786357873578835789357903579135792357933579435795357963579735798357993580035801358023580335804358053580635807358083580935810358113581235813358143581535816358173581835819358203582135822358233582435825358263582735828358293583035831358323583335834358353583635837358383583935840358413584235843358443584535846358473584835849358503585135852358533585435855358563585735858358593586035861358623586335864358653586635867358683586935870358713587235873358743587535876358773587835879358803588135882358833588435885358863588735888358893589035891358923589335894358953589635897358983589935900359013590235903359043590535906359073590835909359103591135912359133591435915359163591735918359193592035921359223592335924359253592635927359283592935930359313593235933359343593535936359373593835939359403594135942359433594435945359463594735948359493595035951359523595335954359553595635957359583595935960359613596235963359643596535966359673596835969359703597135972359733597435975359763597735978359793598035981359823598335984359853598635987359883598935990359913599235993359943599535996359973599835999360003600136002360033600436005360063600736008360093601036011360123601336014360153601636017360183601936020360213602236023360243602536026360273602836029360303603136032360333603436035360363603736038360393604036041360423604336044360453604636047360483604936050360513605236053360543605536056360573605836059360603606136062360633606436065360663606736068360693607036071360723607336074360753607636077360783607936080360813608236083360843608536086360873608836089360903609136092360933609436095360963609736098360993610036101361023610336104361053610636107361083610936110361113611236113361143611536116361173611836119361203612136122361233612436125361263612736128361293613036131361323613336134361353613636137361383613936140361413614236143361443614536146361473614836149361503615136152361533615436155361563615736158361593616036161361623616336164361653616636167361683616936170361713617236173361743617536176361773617836179361803618136182361833618436185361863618736188361893619036191361923619336194361953619636197361983619936200362013620236203362043620536206362073620836209362103621136212362133621436215362163621736218362193622036221362223622336224362253622636227362283622936230362313623236233362343623536236362373623836239362403624136242362433624436245362463624736248362493625036251362523625336254362553625636257362583625936260362613626236263362643626536266362673626836269362703627136272362733627436275362763627736278362793628036281362823628336284362853628636287362883628936290362913629236293362943629536296362973629836299363003630136302363033630436305363063630736308363093631036311363123631336314363153631636317363183631936320363213632236323363243632536326363273632836329363303633136332363333633436335363363633736338363393634036341363423634336344363453634636347363483634936350363513635236353363543635536356363573635836359363603636136362363633636436365363663636736368363693637036371363723637336374363753637636377363783637936380363813638236383363843638536386363873638836389363903639136392363933639436395363963639736398363993640036401364023640336404364053640636407364083640936410364113641236413364143641536416364173641836419364203642136422364233642436425364263642736428364293643036431364323643336434364353643636437364383643936440364413644236443364443644536446364473644836449364503645136452364533645436455364563645736458364593646036461364623646336464364653646636467364683646936470364713647236473364743647536476364773647836479364803648136482364833648436485364863648736488364893649036491364923649336494364953649636497364983649936500365013650236503365043650536506365073650836509365103651136512365133651436515365163651736518365193652036521365223652336524365253652636527365283652936530365313653236533365343653536536365373653836539365403654136542365433654436545365463654736548365493655036551365523655336554365553655636557365583655936560365613656236563365643656536566365673656836569365703657136572365733657436575365763657736578365793658036581365823658336584365853658636587365883658936590365913659236593365943659536596365973659836599366003660136602366033660436605366063660736608366093661036611366123661336614366153661636617366183661936620366213662236623366243662536626366273662836629366303663136632366333663436635366363663736638366393664036641366423664336644366453664636647366483664936650366513665236653366543665536656366573665836659366603666136662366633666436665366663666736668366693667036671366723667336674366753667636677366783667936680366813668236683366843668536686366873668836689366903669136692366933669436695366963669736698366993670036701367023670336704367053670636707367083670936710367113671236713367143671536716367173671836719367203672136722367233672436725367263672736728367293673036731367323673336734367353673636737367383673936740367413674236743367443674536746367473674836749367503675136752367533675436755367563675736758367593676036761367623676336764367653676636767367683676936770367713677236773367743677536776367773677836779367803678136782367833678436785367863678736788367893679036791367923679336794367953679636797367983679936800368013680236803368043680536806368073680836809368103681136812368133681436815368163681736818368193682036821368223682336824368253682636827368283682936830368313683236833368343683536836368373683836839368403684136842368433684436845368463684736848368493685036851368523685336854368553685636857368583685936860368613686236863368643686536866368673686836869368703687136872368733687436875368763687736878368793688036881368823688336884368853688636887368883688936890368913689236893368943689536896368973689836899369003690136902369033690436905369063690736908369093691036911369123691336914369153691636917369183691936920369213692236923369243692536926369273692836929369303693136932369333693436935369363693736938369393694036941369423694336944369453694636947369483694936950369513695236953369543695536956369573695836959369603696136962369633696436965369663696736968369693697036971369723697336974369753697636977369783697936980369813698236983369843698536986369873698836989369903699136992369933699436995369963699736998369993700037001370023700337004370053700637007370083700937010370113701237013370143701537016370173701837019370203702137022370233702437025370263702737028370293703037031370323703337034370353703637037370383703937040370413704237043370443704537046370473704837049370503705137052370533705437055370563705737058370593706037061370623706337064370653706637067370683706937070370713707237073370743707537076370773707837079370803708137082370833708437085370863708737088370893709037091370923709337094370953709637097370983709937100371013710237103371043710537106371073710837109371103711137112371133711437115371163711737118371193712037121371223712337124371253712637127371283712937130371313713237133371343713537136371373713837139371403714137142371433714437145371463714737148371493715037151371523715337154371553715637157371583715937160371613716237163371643716537166371673716837169371703717137172371733717437175371763717737178371793718037181371823718337184371853718637187371883718937190371913719237193371943719537196371973719837199372003720137202372033720437205372063720737208372093721037211372123721337214372153721637217372183721937220372213722237223372243722537226372273722837229372303723137232372333723437235372363723737238372393724037241372423724337244372453724637247372483724937250372513725237253372543725537256372573725837259372603726137262372633726437265372663726737268372693727037271372723727337274372753727637277372783727937280372813728237283372843728537286372873728837289372903729137292372933729437295372963729737298372993730037301373023730337304373053730637307373083730937310373113731237313373143731537316373173731837319373203732137322373233732437325373263732737328373293733037331373323733337334373353733637337373383733937340373413734237343373443734537346373473734837349373503735137352373533735437355373563735737358373593736037361373623736337364373653736637367373683736937370373713737237373373743737537376373773737837379373803738137382373833738437385373863738737388373893739037391373923739337394373953739637397373983739937400374013740237403374043740537406374073740837409374103741137412374133741437415374163741737418374193742037421374223742337424374253742637427374283742937430374313743237433374343743537436374373743837439374403744137442374433744437445374463744737448374493745037451374523745337454374553745637457374583745937460374613746237463374643746537466374673746837469374703747137472374733747437475374763747737478374793748037481374823748337484374853748637487374883748937490374913749237493374943749537496374973749837499375003750137502375033750437505375063750737508375093751037511375123751337514375153751637517375183751937520375213752237523375243752537526375273752837529375303753137532375333753437535375363753737538375393754037541375423754337544375453754637547375483754937550375513755237553375543755537556375573755837559375603756137562375633756437565375663756737568375693757037571375723757337574375753757637577375783757937580375813758237583375843758537586375873758837589375903759137592375933759437595375963759737598375993760037601376023760337604376053760637607376083760937610376113761237613376143761537616376173761837619376203762137622376233762437625376263762737628376293763037631376323763337634376353763637637376383763937640376413764237643376443764537646376473764837649376503765137652376533765437655376563765737658376593766037661376623766337664376653766637667376683766937670376713767237673376743767537676376773767837679376803768137682376833768437685376863768737688376893769037691376923769337694376953769637697376983769937700377013770237703377043770537706377073770837709377103771137712377133771437715377163771737718377193772037721377223772337724377253772637727377283772937730377313773237733377343773537736377373773837739377403774137742377433774437745377463774737748377493775037751377523775337754377553775637757377583775937760377613776237763377643776537766377673776837769377703777137772377733777437775377763777737778377793778037781377823778337784377853778637787377883778937790377913779237793377943779537796377973779837799378003780137802378033780437805378063780737808378093781037811378123781337814378153781637817378183781937820378213782237823378243782537826378273782837829378303783137832378333783437835378363783737838378393784037841378423784337844378453784637847378483784937850378513785237853378543785537856378573785837859378603786137862378633786437865378663786737868378693787037871378723787337874378753787637877378783787937880378813788237883378843788537886378873788837889378903789137892378933789437895378963789737898378993790037901379023790337904379053790637907379083790937910379113791237913379143791537916379173791837919379203792137922379233792437925379263792737928379293793037931379323793337934379353793637937379383793937940379413794237943379443794537946379473794837949379503795137952379533795437955379563795737958379593796037961379623796337964379653796637967379683796937970379713797237973379743797537976379773797837979379803798137982379833798437985379863798737988379893799037991379923799337994379953799637997379983799938000380013800238003380043800538006380073800838009380103801138012380133801438015380163801738018380193802038021380223802338024380253802638027380283802938030380313803238033380343803538036380373803838039380403804138042380433804438045380463804738048380493805038051380523805338054380553805638057380583805938060380613806238063380643806538066380673806838069380703807138072380733807438075380763807738078380793808038081380823808338084380853808638087380883808938090380913809238093380943809538096380973809838099381003810138102381033810438105381063810738108381093811038111381123811338114381153811638117381183811938120381213812238123381243812538126381273812838129381303813138132381333813438135381363813738138381393814038141381423814338144381453814638147381483814938150381513815238153381543815538156381573815838159381603816138162381633816438165381663816738168381693817038171381723817338174381753817638177381783817938180381813818238183381843818538186381873818838189381903819138192381933819438195381963819738198381993820038201382023820338204382053820638207382083820938210382113821238213382143821538216382173821838219382203822138222382233822438225382263822738228382293823038231382323823338234382353823638237382383823938240382413824238243382443824538246382473824838249382503825138252382533825438255382563825738258382593826038261382623826338264382653826638267382683826938270382713827238273382743827538276382773827838279382803828138282382833828438285382863828738288382893829038291382923829338294382953829638297382983829938300383013830238303383043830538306383073830838309383103831138312383133831438315383163831738318383193832038321383223832338324383253832638327383283832938330383313833238333383343833538336383373833838339383403834138342383433834438345383463834738348383493835038351383523835338354383553835638357383583835938360383613836238363383643836538366383673836838369383703837138372383733837438375383763837738378383793838038381383823838338384383853838638387383883838938390383913839238393383943839538396383973839838399384003840138402384033840438405384063840738408384093841038411384123841338414384153841638417384183841938420384213842238423384243842538426384273842838429384303843138432384333843438435384363843738438384393844038441384423844338444384453844638447384483844938450384513845238453384543845538456384573845838459384603846138462384633846438465384663846738468384693847038471384723847338474384753847638477384783847938480384813848238483384843848538486384873848838489384903849138492384933849438495384963849738498384993850038501385023850338504385053850638507385083850938510385113851238513385143851538516385173851838519385203852138522385233852438525385263852738528385293853038531385323853338534385353853638537385383853938540385413854238543385443854538546385473854838549385503855138552385533855438555385563855738558385593856038561385623856338564385653856638567385683856938570385713857238573385743857538576385773857838579385803858138582385833858438585385863858738588385893859038591385923859338594385953859638597385983859938600386013860238603386043860538606386073860838609386103861138612386133861438615386163861738618386193862038621386223862338624386253862638627386283862938630386313863238633386343863538636386373863838639386403864138642386433864438645386463864738648386493865038651386523865338654386553865638657386583865938660386613866238663386643866538666386673866838669386703867138672386733867438675386763867738678386793868038681386823868338684386853868638687386883868938690386913869238693386943869538696386973869838699387003870138702387033870438705387063870738708387093871038711387123871338714387153871638717387183871938720387213872238723387243872538726387273872838729387303873138732387333873438735387363873738738387393874038741387423874338744387453874638747387483874938750387513875238753387543875538756387573875838759387603876138762387633876438765387663876738768387693877038771387723877338774387753877638777387783877938780387813878238783387843878538786387873878838789387903879138792387933879438795387963879738798387993880038801388023880338804388053880638807388083880938810388113881238813388143881538816388173881838819388203882138822388233882438825388263882738828388293883038831388323883338834388353883638837388383883938840388413884238843388443884538846388473884838849388503885138852388533885438855388563885738858388593886038861388623886338864388653886638867388683886938870388713887238873388743887538876388773887838879388803888138882388833888438885388863888738888388893889038891388923889338894388953889638897388983889938900389013890238903389043890538906389073890838909389103891138912389133891438915389163891738918389193892038921389223892338924389253892638927389283892938930389313893238933389343893538936389373893838939389403894138942389433894438945389463894738948389493895038951389523895338954389553895638957389583895938960389613896238963389643896538966389673896838969389703897138972389733897438975389763897738978389793898038981389823898338984389853898638987389883898938990389913899238993389943899538996389973899838999390003900139002390033900439005390063900739008390093901039011390123901339014390153901639017390183901939020390213902239023390243902539026390273902839029390303903139032390333903439035390363903739038390393904039041390423904339044390453904639047390483904939050390513905239053390543905539056390573905839059390603906139062390633906439065390663906739068390693907039071390723907339074390753907639077390783907939080390813908239083390843908539086390873908839089390903909139092390933909439095390963909739098390993910039101391023910339104391053910639107391083910939110391113911239113391143911539116391173911839119391203912139122391233912439125391263912739128391293913039131391323913339134391353913639137391383913939140391413914239143391443914539146391473914839149391503915139152391533915439155391563915739158391593916039161391623916339164391653916639167391683916939170391713917239173391743917539176391773917839179391803918139182391833918439185391863918739188391893919039191391923919339194391953919639197391983919939200392013920239203392043920539206392073920839209392103921139212392133921439215392163921739218392193922039221392223922339224392253922639227392283922939230392313923239233392343923539236392373923839239392403924139242392433924439245392463924739248392493925039251392523925339254392553925639257392583925939260392613926239263392643926539266392673926839269392703927139272392733927439275392763927739278392793928039281392823928339284392853928639287392883928939290392913929239293392943929539296392973929839299393003930139302393033930439305393063930739308393093931039311393123931339314393153931639317393183931939320393213932239323393243932539326393273932839329393303933139332393333933439335393363933739338393393934039341393423934339344393453934639347393483934939350393513935239353393543935539356393573935839359393603936139362393633936439365393663936739368393693937039371393723937339374393753937639377393783937939380393813938239383393843938539386393873938839389393903939139392393933939439395393963939739398393993940039401394023940339404394053940639407394083940939410394113941239413394143941539416394173941839419394203942139422394233942439425394263942739428394293943039431394323943339434394353943639437394383943939440394413944239443394443944539446394473944839449394503945139452394533945439455394563945739458394593946039461394623946339464394653946639467394683946939470394713947239473394743947539476394773947839479394803948139482394833948439485394863948739488394893949039491394923949339494394953949639497394983949939500395013950239503395043950539506395073950839509395103951139512395133951439515395163951739518395193952039521395223952339524395253952639527395283952939530395313953239533395343953539536395373953839539395403954139542395433954439545395463954739548395493955039551395523955339554395553955639557395583955939560395613956239563395643956539566395673956839569395703957139572395733957439575395763957739578395793958039581395823958339584395853958639587395883958939590395913959239593395943959539596395973959839599396003960139602396033960439605396063960739608396093961039611396123961339614396153961639617396183961939620396213962239623396243962539626396273962839629396303963139632396333963439635396363963739638396393964039641396423964339644396453964639647396483964939650396513965239653396543965539656396573965839659396603966139662396633966439665396663966739668396693967039671396723967339674396753967639677396783967939680396813968239683396843968539686396873968839689396903969139692396933969439695396963969739698396993970039701397023970339704397053970639707397083970939710397113971239713397143971539716397173971839719397203972139722397233972439725397263972739728397293973039731397323973339734397353973639737397383973939740397413974239743397443974539746397473974839749397503975139752397533975439755397563975739758397593976039761397623976339764397653976639767397683976939770397713977239773397743977539776397773977839779397803978139782397833978439785397863978739788397893979039791397923979339794397953979639797397983979939800398013980239803398043980539806398073980839809398103981139812398133981439815398163981739818398193982039821398223982339824398253982639827398283982939830398313983239833398343983539836398373983839839398403984139842398433984439845398463984739848398493985039851398523985339854398553985639857398583985939860398613986239863398643986539866398673986839869398703987139872398733987439875398763987739878398793988039881398823988339884398853988639887398883988939890398913989239893398943989539896398973989839899399003990139902399033990439905399063990739908399093991039911399123991339914399153991639917399183991939920399213992239923399243992539926399273992839929399303993139932399333993439935399363993739938399393994039941399423994339944399453994639947399483994939950399513995239953399543995539956399573995839959399603996139962399633996439965399663996739968399693997039971399723997339974399753997639977399783997939980399813998239983399843998539986399873998839989399903999139992399933999439995399963999739998399994000040001400024000340004400054000640007400084000940010400114001240013400144001540016400174001840019400204002140022400234002440025400264002740028400294003040031400324003340034400354003640037400384003940040400414004240043400444004540046400474004840049400504005140052400534005440055400564005740058400594006040061400624006340064400654006640067400684006940070400714007240073400744007540076400774007840079400804008140082400834008440085400864008740088400894009040091400924009340094400954009640097400984009940100401014010240103401044010540106401074010840109401104011140112401134011440115401164011740118401194012040121401224012340124401254012640127401284012940130401314013240133401344013540136401374013840139401404014140142401434014440145401464014740148401494015040151401524015340154401554015640157401584015940160401614016240163401644016540166401674016840169401704017140172401734017440175401764017740178401794018040181401824018340184401854018640187401884018940190401914019240193401944019540196401974019840199402004020140202402034020440205402064020740208402094021040211402124021340214402154021640217402184021940220402214022240223402244022540226402274022840229402304023140232402334023440235402364023740238402394024040241402424024340244402454024640247402484024940250402514025240253402544025540256402574025840259402604026140262402634026440265402664026740268402694027040271402724027340274402754027640277402784027940280402814028240283402844028540286402874028840289402904029140292402934029440295402964029740298402994030040301403024030340304403054030640307403084030940310403114031240313403144031540316403174031840319403204032140322403234032440325403264032740328403294033040331403324033340334403354033640337403384033940340403414034240343403444034540346403474034840349403504035140352403534035440355403564035740358403594036040361403624036340364403654036640367403684036940370403714037240373403744037540376403774037840379403804038140382403834038440385403864038740388403894039040391403924039340394403954039640397403984039940400404014040240403404044040540406404074040840409404104041140412404134041440415404164041740418404194042040421404224042340424404254042640427404284042940430404314043240433404344043540436404374043840439404404044140442404434044440445404464044740448404494045040451404524045340454404554045640457404584045940460404614046240463404644046540466404674046840469404704047140472404734047440475404764047740478404794048040481404824048340484404854048640487404884048940490404914049240493404944049540496404974049840499405004050140502405034050440505405064050740508405094051040511405124051340514405154051640517405184051940520405214052240523405244052540526405274052840529405304053140532405334053440535405364053740538405394054040541405424054340544405454054640547405484054940550405514055240553405544055540556405574055840559405604056140562405634056440565405664056740568405694057040571405724057340574405754057640577405784057940580405814058240583405844058540586405874058840589405904059140592405934059440595405964059740598405994060040601406024060340604406054060640607406084060940610406114061240613406144061540616406174061840619406204062140622406234062440625406264062740628406294063040631406324063340634406354063640637406384063940640406414064240643406444064540646406474064840649406504065140652406534065440655406564065740658406594066040661406624066340664406654066640667406684066940670406714067240673406744067540676406774067840679406804068140682406834068440685406864068740688406894069040691406924069340694406954069640697406984069940700407014070240703407044070540706407074070840709407104071140712407134071440715407164071740718407194072040721407224072340724407254072640727407284072940730407314073240733407344073540736407374073840739407404074140742407434074440745407464074740748407494075040751407524075340754407554075640757407584075940760407614076240763407644076540766407674076840769407704077140772407734077440775407764077740778407794078040781407824078340784407854078640787407884078940790407914079240793407944079540796407974079840799408004080140802408034080440805408064080740808408094081040811408124081340814408154081640817408184081940820408214082240823408244082540826408274082840829408304083140832408334083440835408364083740838408394084040841408424084340844408454084640847408484084940850408514085240853408544085540856408574085840859408604086140862408634086440865408664086740868408694087040871408724087340874408754087640877408784087940880408814088240883408844088540886408874088840889408904089140892408934089440895408964089740898408994090040901409024090340904409054090640907409084090940910409114091240913409144091540916409174091840919409204092140922409234092440925409264092740928409294093040931409324093340934409354093640937409384093940940409414094240943409444094540946409474094840949409504095140952409534095440955409564095740958409594096040961409624096340964409654096640967409684096940970409714097240973409744097540976409774097840979409804098140982409834098440985409864098740988409894099040991409924099340994409954099640997409984099941000410014100241003410044100541006410074100841009410104101141012410134101441015410164101741018410194102041021410224102341024410254102641027410284102941030410314103241033410344103541036410374103841039410404104141042410434104441045410464104741048410494105041051410524105341054410554105641057410584105941060410614106241063410644106541066410674106841069410704107141072410734107441075410764107741078410794108041081410824108341084410854108641087410884108941090410914109241093410944109541096410974109841099411004110141102411034110441105411064110741108411094111041111411124111341114411154111641117411184111941120411214112241123411244112541126411274112841129411304113141132411334113441135411364113741138411394114041141411424114341144411454114641147411484114941150411514115241153411544115541156411574115841159411604116141162411634116441165411664116741168411694117041171411724117341174411754117641177411784117941180411814118241183411844118541186411874118841189411904119141192411934119441195411964119741198411994120041201412024120341204412054120641207412084120941210412114121241213412144121541216412174121841219412204122141222412234122441225412264122741228412294123041231412324123341234412354123641237412384123941240412414124241243412444124541246412474124841249412504125141252412534125441255412564125741258412594126041261412624126341264412654126641267412684126941270412714127241273412744127541276412774127841279412804128141282412834128441285412864128741288412894129041291412924129341294412954129641297412984129941300413014130241303413044130541306413074130841309413104131141312413134131441315413164131741318413194132041321413224132341324413254132641327413284132941330413314133241333413344133541336413374133841339413404134141342413434134441345413464134741348413494135041351413524135341354413554135641357413584135941360413614136241363413644136541366413674136841369413704137141372413734137441375413764137741378413794138041381413824138341384413854138641387413884138941390413914139241393413944139541396413974139841399414004140141402414034140441405414064140741408414094141041411414124141341414414154141641417414184141941420414214142241423414244142541426414274142841429414304143141432414334143441435414364143741438414394144041441414424144341444414454144641447414484144941450414514145241453414544145541456414574145841459414604146141462414634146441465414664146741468414694147041471414724147341474414754147641477414784147941480414814148241483414844148541486414874148841489414904149141492414934149441495414964149741498414994150041501415024150341504415054150641507415084150941510415114151241513415144151541516415174151841519415204152141522415234152441525415264152741528415294153041531415324153341534415354153641537415384153941540415414154241543415444154541546415474154841549415504155141552415534155441555415564155741558415594156041561415624156341564415654156641567415684156941570415714157241573415744157541576415774157841579415804158141582415834158441585415864158741588415894159041591415924159341594415954159641597415984159941600416014160241603416044160541606416074160841609416104161141612416134161441615416164161741618416194162041621416224162341624416254162641627416284162941630416314163241633416344163541636416374163841639416404164141642416434164441645416464164741648416494165041651416524165341654416554165641657416584165941660416614166241663416644166541666416674166841669416704167141672416734167441675416764167741678416794168041681416824168341684416854168641687416884168941690416914169241693416944169541696416974169841699417004170141702417034170441705417064170741708417094171041711417124171341714417154171641717417184171941720417214172241723417244172541726417274172841729417304173141732417334173441735417364173741738417394174041741417424174341744417454174641747417484174941750417514175241753417544175541756417574175841759417604176141762417634176441765417664176741768417694177041771417724177341774417754177641777417784177941780417814178241783417844178541786417874178841789417904179141792417934179441795417964179741798417994180041801418024180341804418054180641807418084180941810418114181241813418144181541816418174181841819418204182141822418234182441825418264182741828418294183041831418324183341834418354183641837418384183941840418414184241843418444184541846418474184841849418504185141852418534185441855418564185741858418594186041861418624186341864418654186641867418684186941870418714187241873418744187541876418774187841879418804188141882418834188441885418864188741888418894189041891418924189341894418954189641897418984189941900419014190241903419044190541906419074190841909419104191141912419134191441915419164191741918419194192041921419224192341924419254192641927419284192941930419314193241933419344193541936419374193841939419404194141942419434194441945419464194741948419494195041951419524195341954419554195641957419584195941960419614196241963419644196541966419674196841969419704197141972419734197441975419764197741978419794198041981419824198341984419854198641987419884198941990419914199241993419944199541996419974199841999420004200142002420034200442005420064200742008420094201042011420124201342014420154201642017420184201942020420214202242023420244202542026420274202842029420304203142032420334203442035420364203742038420394204042041420424204342044420454204642047420484204942050420514205242053420544205542056420574205842059420604206142062420634206442065420664206742068420694207042071420724207342074420754207642077420784207942080420814208242083420844208542086420874208842089420904209142092420934209442095420964209742098420994210042101421024210342104421054210642107421084210942110421114211242113421144211542116421174211842119421204212142122421234212442125421264212742128421294213042131421324213342134421354213642137421384213942140421414214242143421444214542146421474214842149421504215142152421534215442155421564215742158421594216042161421624216342164421654216642167421684216942170421714217242173421744217542176421774217842179421804218142182421834218442185421864218742188421894219042191421924219342194421954219642197421984219942200422014220242203422044220542206422074220842209422104221142212422134221442215422164221742218422194222042221422224222342224422254222642227422284222942230422314223242233422344223542236422374223842239422404224142242422434224442245422464224742248422494225042251422524225342254422554225642257422584225942260422614226242263422644226542266422674226842269422704227142272422734227442275422764227742278422794228042281422824228342284422854228642287422884228942290422914229242293422944229542296422974229842299423004230142302423034230442305423064230742308423094231042311423124231342314423154231642317423184231942320423214232242323423244232542326423274232842329423304233142332423334233442335423364233742338423394234042341423424234342344423454234642347423484234942350423514235242353423544235542356423574235842359423604236142362423634236442365423664236742368423694237042371423724237342374423754237642377423784237942380423814238242383423844238542386423874238842389423904239142392423934239442395423964239742398423994240042401424024240342404424054240642407424084240942410424114241242413424144241542416424174241842419424204242142422424234242442425424264242742428424294243042431424324243342434424354243642437424384243942440424414244242443424444244542446424474244842449424504245142452424534245442455424564245742458424594246042461424624246342464424654246642467424684246942470424714247242473424744247542476424774247842479424804248142482424834248442485424864248742488424894249042491424924249342494424954249642497424984249942500425014250242503425044250542506425074250842509425104251142512425134251442515425164251742518425194252042521425224252342524425254252642527425284252942530425314253242533425344253542536425374253842539425404254142542425434254442545425464254742548425494255042551425524255342554425554255642557425584255942560425614256242563425644256542566425674256842569425704257142572425734257442575425764257742578425794258042581425824258342584425854258642587425884258942590425914259242593425944259542596425974259842599426004260142602426034260442605426064260742608426094261042611426124261342614426154261642617426184261942620426214262242623426244262542626426274262842629426304263142632426334263442635426364263742638426394264042641426424264342644426454264642647426484264942650426514265242653426544265542656426574265842659426604266142662426634266442665426664266742668426694267042671426724267342674426754267642677426784267942680426814268242683426844268542686426874268842689426904269142692426934269442695426964269742698426994270042701427024270342704427054270642707427084270942710427114271242713427144271542716427174271842719427204272142722427234272442725427264272742728427294273042731427324273342734427354273642737427384273942740427414274242743427444274542746427474274842749427504275142752427534275442755427564275742758427594276042761427624276342764427654276642767427684276942770427714277242773427744277542776427774277842779427804278142782427834278442785427864278742788427894279042791427924279342794427954279642797427984279942800428014280242803428044280542806428074280842809428104281142812428134281442815428164281742818428194282042821428224282342824428254282642827428284282942830428314283242833428344283542836428374283842839428404284142842428434284442845428464284742848428494285042851428524285342854428554285642857428584285942860428614286242863428644286542866428674286842869428704287142872428734287442875428764287742878428794288042881428824288342884428854288642887428884288942890428914289242893428944289542896428974289842899429004290142902429034290442905429064290742908429094291042911429124291342914429154291642917429184291942920429214292242923429244292542926429274292842929429304293142932429334293442935429364293742938429394294042941429424294342944429454294642947429484294942950429514295242953429544295542956429574295842959429604296142962429634296442965429664296742968429694297042971429724297342974429754297642977429784297942980429814298242983429844298542986429874298842989429904299142992429934299442995429964299742998429994300043001430024300343004430054300643007430084300943010430114301243013430144301543016430174301843019430204302143022430234302443025430264302743028430294303043031430324303343034430354303643037430384303943040430414304243043430444304543046430474304843049430504305143052430534305443055430564305743058430594306043061430624306343064430654306643067430684306943070430714307243073430744307543076430774307843079430804308143082430834308443085430864308743088430894309043091430924309343094430954309643097430984309943100431014310243103431044310543106431074310843109431104311143112431134311443115431164311743118431194312043121431224312343124431254312643127431284312943130431314313243133431344313543136431374313843139431404314143142431434314443145431464314743148431494315043151431524315343154431554315643157431584315943160431614316243163431644316543166431674316843169431704317143172431734317443175431764317743178431794318043181431824318343184431854318643187431884318943190431914319243193431944319543196431974319843199432004320143202432034320443205432064320743208432094321043211432124321343214432154321643217432184321943220432214322243223432244322543226432274322843229432304323143232432334323443235432364323743238432394324043241432424324343244432454324643247432484324943250432514325243253432544325543256432574325843259432604326143262432634326443265432664326743268432694327043271432724327343274432754327643277432784327943280432814328243283432844328543286432874328843289432904329143292432934329443295432964329743298432994330043301433024330343304433054330643307433084330943310433114331243313433144331543316433174331843319433204332143322433234332443325433264332743328433294333043331433324333343334433354333643337433384333943340433414334243343433444334543346433474334843349433504335143352433534335443355433564335743358433594336043361433624336343364433654336643367433684336943370433714337243373433744337543376433774337843379433804338143382433834338443385433864338743388433894339043391433924339343394433954339643397433984339943400434014340243403434044340543406434074340843409434104341143412434134341443415434164341743418434194342043421434224342343424434254342643427434284342943430434314343243433434344343543436434374343843439434404344143442434434344443445434464344743448434494345043451434524345343454434554345643457434584345943460434614346243463434644346543466434674346843469434704347143472434734347443475434764347743478434794348043481434824348343484434854348643487434884348943490434914349243493434944349543496434974349843499435004350143502435034350443505435064350743508435094351043511435124351343514435154351643517435184351943520435214352243523435244352543526435274352843529435304353143532435334353443535435364353743538435394354043541435424354343544435454354643547435484354943550435514355243553435544355543556435574355843559435604356143562435634356443565435664356743568435694357043571435724357343574435754357643577435784357943580435814358243583435844358543586435874358843589435904359143592435934359443595435964359743598435994360043601436024360343604436054360643607436084360943610436114361243613436144361543616436174361843619436204362143622436234362443625436264362743628436294363043631436324363343634436354363643637436384363943640436414364243643436444364543646436474364843649436504365143652436534365443655436564365743658436594366043661436624366343664436654366643667436684366943670436714367243673436744367543676436774367843679436804368143682436834368443685436864368743688436894369043691436924369343694436954369643697436984369943700437014370243703437044370543706437074370843709437104371143712437134371443715437164371743718437194372043721437224372343724437254372643727437284372943730437314373243733437344373543736437374373843739437404374143742437434374443745437464374743748437494375043751437524375343754437554375643757437584375943760437614376243763437644376543766437674376843769437704377143772437734377443775437764377743778437794378043781437824378343784437854378643787437884378943790437914379243793437944379543796437974379843799438004380143802438034380443805438064380743808438094381043811438124381343814438154381643817438184381943820438214382243823438244382543826438274382843829438304383143832438334383443835438364383743838438394384043841438424384343844438454384643847438484384943850438514385243853438544385543856438574385843859438604386143862438634386443865438664386743868438694387043871438724387343874438754387643877438784387943880438814388243883438844388543886438874388843889438904389143892438934389443895438964389743898438994390043901439024390343904439054390643907439084390943910439114391243913439144391543916439174391843919439204392143922439234392443925439264392743928439294393043931439324393343934439354393643937439384393943940439414394243943439444394543946439474394843949439504395143952439534395443955439564395743958439594396043961439624396343964439654396643967439684396943970439714397243973439744397543976439774397843979439804398143982439834398443985439864398743988439894399043991439924399343994439954399643997439984399944000440014400244003440044400544006440074400844009440104401144012440134401444015440164401744018440194402044021440224402344024440254402644027440284402944030440314403244033440344403544036440374403844039440404404144042440434404444045440464404744048440494405044051440524405344054440554405644057440584405944060440614406244063440644406544066440674406844069440704407144072440734407444075440764407744078440794408044081440824408344084440854408644087440884408944090440914409244093440944409544096440974409844099441004410144102441034410444105441064410744108441094411044111441124411344114441154411644117441184411944120441214412244123441244412544126441274412844129441304413144132441334413444135441364413744138441394414044141441424414344144441454414644147441484414944150441514415244153441544415544156441574415844159441604416144162441634416444165441664416744168441694417044171441724417344174441754417644177441784417944180441814418244183441844418544186441874418844189441904419144192441934419444195441964419744198441994420044201442024420344204442054420644207442084420944210442114421244213442144421544216442174421844219442204422144222442234422444225442264422744228442294423044231442324423344234442354423644237442384423944240442414424244243442444424544246442474424844249442504425144252442534425444255442564425744258442594426044261442624426344264442654426644267442684426944270442714427244273442744427544276442774427844279442804428144282442834428444285442864428744288442894429044291442924429344294442954429644297442984429944300443014430244303443044430544306443074430844309443104431144312443134431444315443164431744318443194432044321443224432344324443254432644327443284432944330443314433244333443344433544336443374433844339443404434144342443434434444345443464434744348443494435044351443524435344354443554435644357443584435944360443614436244363443644436544366443674436844369443704437144372443734437444375443764437744378443794438044381443824438344384443854438644387443884438944390443914439244393443944439544396443974439844399444004440144402444034440444405444064440744408444094441044411444124441344414444154441644417444184441944420444214442244423444244442544426444274442844429444304443144432444334443444435444364443744438444394444044441444424444344444444454444644447444484444944450444514445244453444544445544456444574445844459444604446144462444634446444465444664446744468444694447044471444724447344474444754447644477444784447944480444814448244483444844448544486444874448844489444904449144492444934449444495444964449744498444994450044501445024450344504445054450644507445084450944510445114451244513445144451544516445174451844519445204452144522445234452444525445264452744528445294453044531445324453344534445354453644537445384453944540445414454244543445444454544546445474454844549445504455144552445534455444555445564455744558445594456044561445624456344564445654456644567445684456944570445714457244573445744457544576445774457844579445804458144582445834458444585445864458744588445894459044591445924459344594445954459644597445984459944600446014460244603446044460544606446074460844609446104461144612446134461444615446164461744618446194462044621446224462344624446254462644627446284462944630446314463244633446344463544636446374463844639446404464144642446434464444645446464464744648446494465044651446524465344654446554465644657446584465944660446614466244663446644466544666446674466844669446704467144672446734467444675446764467744678446794468044681446824468344684446854468644687446884468944690446914469244693446944469544696446974469844699447004470144702447034470444705447064470744708447094471044711447124471344714447154471644717447184471944720447214472244723447244472544726447274472844729447304473144732447334473444735447364473744738447394474044741447424474344744447454474644747447484474944750447514475244753447544475544756447574475844759447604476144762447634476444765447664476744768447694477044771447724477344774447754477644777447784477944780447814478244783447844478544786447874478844789447904479144792447934479444795447964479744798447994480044801448024480344804448054480644807448084480944810448114481244813448144481544816448174481844819448204482144822448234482444825448264482744828448294483044831448324483344834448354483644837448384483944840448414484244843448444484544846448474484844849448504485144852448534485444855448564485744858448594486044861448624486344864448654486644867448684486944870448714487244873448744487544876448774487844879448804488144882448834488444885448864488744888448894489044891448924489344894448954489644897448984489944900449014490244903449044490544906449074490844909449104491144912449134491444915449164491744918449194492044921449224492344924449254492644927449284492944930449314493244933449344493544936449374493844939449404494144942449434494444945449464494744948449494495044951449524495344954449554495644957449584495944960449614496244963449644496544966449674496844969449704497144972449734497444975449764497744978449794498044981449824498344984449854498644987449884498944990449914499244993449944499544996449974499844999450004500145002450034500445005450064500745008450094501045011450124501345014450154501645017450184501945020450214502245023450244502545026450274502845029450304503145032450334503445035450364503745038450394504045041450424504345044450454504645047450484504945050450514505245053450544505545056450574505845059450604506145062450634506445065450664506745068450694507045071450724507345074450754507645077450784507945080450814508245083450844508545086450874508845089450904509145092450934509445095450964509745098450994510045101451024510345104451054510645107451084510945110451114511245113451144511545116451174511845119451204512145122451234512445125451264512745128451294513045131451324513345134451354513645137451384513945140451414514245143451444514545146451474514845149451504515145152451534515445155451564515745158451594516045161451624516345164451654516645167451684516945170451714517245173451744517545176451774517845179451804518145182451834518445185451864518745188451894519045191451924519345194451954519645197451984519945200452014520245203452044520545206452074520845209452104521145212452134521445215452164521745218452194522045221452224522345224452254522645227452284522945230452314523245233452344523545236452374523845239452404524145242452434524445245452464524745248452494525045251452524525345254452554525645257452584525945260452614526245263452644526545266452674526845269452704527145272452734527445275452764527745278452794528045281452824528345284452854528645287452884528945290452914529245293452944529545296452974529845299453004530145302453034530445305453064530745308453094531045311453124531345314453154531645317453184531945320453214532245323453244532545326453274532845329453304533145332453334533445335453364533745338453394534045341453424534345344453454534645347453484534945350453514535245353453544535545356453574535845359453604536145362453634536445365453664536745368453694537045371453724537345374453754537645377453784537945380453814538245383453844538545386453874538845389453904539145392453934539445395453964539745398453994540045401454024540345404454054540645407454084540945410454114541245413454144541545416454174541845419454204542145422454234542445425454264542745428454294543045431454324543345434454354543645437454384543945440454414544245443454444544545446454474544845449454504545145452454534545445455454564545745458454594546045461454624546345464454654546645467454684546945470454714547245473454744547545476454774547845479454804548145482454834548445485454864548745488454894549045491454924549345494454954549645497454984549945500455014550245503455044550545506455074550845509455104551145512455134551445515455164551745518455194552045521455224552345524455254552645527455284552945530455314553245533455344553545536455374553845539455404554145542455434554445545455464554745548455494555045551455524555345554455554555645557455584555945560455614556245563455644556545566455674556845569455704557145572455734557445575455764557745578455794558045581455824558345584455854558645587455884558945590455914559245593455944559545596455974559845599456004560145602456034560445605456064560745608456094561045611456124561345614456154561645617456184561945620456214562245623456244562545626456274562845629456304563145632456334563445635456364563745638456394564045641456424564345644456454564645647456484564945650456514565245653456544565545656456574565845659456604566145662456634566445665456664566745668456694567045671456724567345674456754567645677456784567945680456814568245683456844568545686456874568845689456904569145692456934569445695456964569745698456994570045701457024570345704457054570645707457084570945710457114571245713457144571545716457174571845719457204572145722457234572445725457264572745728457294573045731457324573345734457354573645737457384573945740457414574245743457444574545746457474574845749457504575145752457534575445755457564575745758457594576045761457624576345764457654576645767457684576945770457714577245773457744577545776457774577845779457804578145782457834578445785457864578745788457894579045791457924579345794457954579645797457984579945800458014580245803458044580545806458074580845809458104581145812458134581445815458164581745818458194582045821458224582345824458254582645827458284582945830458314583245833458344583545836458374583845839458404584145842458434584445845458464584745848458494585045851458524585345854458554585645857458584585945860458614586245863458644586545866458674586845869458704587145872458734587445875458764587745878458794588045881458824588345884458854588645887458884588945890458914589245893458944589545896458974589845899459004590145902459034590445905459064590745908459094591045911459124591345914459154591645917459184591945920459214592245923459244592545926459274592845929459304593145932459334593445935459364593745938459394594045941459424594345944459454594645947459484594945950459514595245953459544595545956459574595845959459604596145962459634596445965459664596745968459694597045971459724597345974459754597645977459784597945980459814598245983459844598545986459874598845989459904599145992459934599445995459964599745998459994600046001460024600346004460054600646007460084600946010460114601246013460144601546016460174601846019460204602146022460234602446025460264602746028460294603046031460324603346034460354603646037460384603946040460414604246043460444604546046460474604846049460504605146052460534605446055460564605746058460594606046061460624606346064460654606646067460684606946070460714607246073460744607546076460774607846079460804608146082460834608446085460864608746088460894609046091460924609346094460954609646097460984609946100461014610246103461044610546106461074610846109461104611146112461134611446115461164611746118461194612046121461224612346124461254612646127461284612946130461314613246133461344613546136461374613846139461404614146142461434614446145461464614746148461494615046151461524615346154461554615646157461584615946160461614616246163461644616546166461674616846169461704617146172461734617446175461764617746178461794618046181461824618346184461854618646187461884618946190461914619246193461944619546196461974619846199462004620146202462034620446205462064620746208462094621046211462124621346214462154621646217462184621946220462214622246223462244622546226462274622846229462304623146232462334623446235462364623746238462394624046241462424624346244462454624646247462484624946250462514625246253462544625546256462574625846259462604626146262462634626446265462664626746268462694627046271462724627346274462754627646277462784627946280462814628246283462844628546286462874628846289462904629146292462934629446295462964629746298462994630046301463024630346304463054630646307463084630946310463114631246313463144631546316463174631846319463204632146322463234632446325463264632746328463294633046331463324633346334463354633646337463384633946340463414634246343463444634546346463474634846349463504635146352463534635446355463564635746358463594636046361463624636346364463654636646367463684636946370463714637246373463744637546376463774637846379463804638146382463834638446385463864638746388463894639046391463924639346394463954639646397463984639946400464014640246403464044640546406464074640846409464104641146412464134641446415464164641746418464194642046421464224642346424464254642646427464284642946430464314643246433464344643546436464374643846439464404644146442464434644446445464464644746448464494645046451464524645346454464554645646457464584645946460464614646246463464644646546466464674646846469464704647146472464734647446475464764647746478464794648046481464824648346484464854648646487464884648946490464914649246493464944649546496464974649846499465004650146502465034650446505465064650746508465094651046511465124651346514465154651646517465184651946520465214652246523465244652546526465274652846529465304653146532465334653446535465364653746538465394654046541465424654346544465454654646547465484654946550465514655246553465544655546556465574655846559465604656146562465634656446565465664656746568465694657046571465724657346574465754657646577465784657946580465814658246583465844658546586465874658846589465904659146592465934659446595465964659746598465994660046601466024660346604466054660646607466084660946610466114661246613466144661546616466174661846619466204662146622466234662446625466264662746628466294663046631466324663346634466354663646637466384663946640466414664246643466444664546646466474664846649466504665146652466534665446655466564665746658466594666046661466624666346664466654666646667466684666946670466714667246673466744667546676466774667846679466804668146682466834668446685466864668746688466894669046691466924669346694466954669646697466984669946700467014670246703467044670546706467074670846709467104671146712467134671446715467164671746718467194672046721467224672346724467254672646727467284672946730467314673246733467344673546736467374673846739467404674146742467434674446745467464674746748467494675046751467524675346754467554675646757467584675946760467614676246763467644676546766467674676846769467704677146772467734677446775467764677746778467794678046781467824678346784467854678646787467884678946790467914679246793467944679546796467974679846799468004680146802468034680446805468064680746808468094681046811468124681346814468154681646817468184681946820468214682246823468244682546826468274682846829468304683146832468334683446835468364683746838468394684046841468424684346844468454684646847468484684946850468514685246853468544685546856468574685846859468604686146862468634686446865468664686746868468694687046871468724687346874468754687646877468784687946880468814688246883468844688546886468874688846889468904689146892468934689446895468964689746898468994690046901469024690346904469054690646907469084690946910469114691246913469144691546916469174691846919469204692146922 |
- // 4.4.1 (2016-07-26)
- /**
- * Compiled inline version. (Library mode)
- */
- /*jshint smarttabs:true, undef:true, latedef:true, curly:true, bitwise:true, camelcase:true */
- /*globals $code */
- (function(exports, undefined) {
- "use strict";
- var modules = {};
- function require(ids, callback) {
- var module, defs = [];
- for (var i = 0; i < ids.length; ++i) {
- module = modules[ids[i]] || resolve(ids[i]);
- if (!module) {
- throw 'module definition dependecy not found: ' + ids[i];
- }
- defs.push(module);
- }
- callback.apply(null, defs);
- }
- function define(id, dependencies, definition) {
- if (typeof id !== 'string') {
- throw 'invalid module definition, module id must be defined and be a string';
- }
- if (dependencies === undefined) {
- throw 'invalid module definition, dependencies must be specified';
- }
- if (definition === undefined) {
- throw 'invalid module definition, definition function must be specified';
- }
- require(dependencies, function() {
- modules[id] = definition.apply(null, arguments);
- });
- }
- function defined(id) {
- return !!modules[id];
- }
- function resolve(id) {
- var target = exports;
- var fragments = id.split(/[.\/]/);
- for (var fi = 0; fi < fragments.length; ++fi) {
- if (!target[fragments[fi]]) {
- return;
- }
- target = target[fragments[fi]];
- }
- return target;
- }
- function expose(ids) {
- var i, target, id, fragments, privateModules;
- for (i = 0; i < ids.length; i++) {
- target = exports;
- id = ids[i];
- fragments = id.split(/[.\/]/);
- for (var fi = 0; fi < fragments.length - 1; ++fi) {
- if (target[fragments[fi]] === undefined) {
- target[fragments[fi]] = {};
- }
- target = target[fragments[fi]];
- }
- target[fragments[fragments.length - 1]] = modules[id];
- }
-
- // Expose private modules for unit tests
- if (exports.AMDLC_TESTS) {
- privateModules = exports.privateModules || {};
- for (id in modules) {
- privateModules[id] = modules[id];
- }
- for (i = 0; i < ids.length; i++) {
- delete privateModules[ids[i]];
- }
- exports.privateModules = privateModules;
- }
- }
- // Included from: js/tinymce/classes/geom/Rect.js
- /**
- * Rect.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Contains various tools for rect/position calculation.
- *
- * @class tinymce.geom.Rect
- */
- define("tinymce/geom/Rect", [
- ], function() {
- "use strict";
- var min = Math.min, max = Math.max, round = Math.round;
- /**
- * Returns the rect positioned based on the relative position name
- * to the target rect.
- *
- * @method relativePosition
- * @param {Rect} rect Source rect to modify into a new rect.
- * @param {Rect} targetRect Rect to move relative to based on the rel option.
- * @param {String} rel Relative position. For example: tr-bl.
- */
- function relativePosition(rect, targetRect, rel) {
- var x, y, w, h, targetW, targetH;
- x = targetRect.x;
- y = targetRect.y;
- w = rect.w;
- h = rect.h;
- targetW = targetRect.w;
- targetH = targetRect.h;
- rel = (rel || '').split('');
- if (rel[0] === 'b') {
- y += targetH;
- }
- if (rel[1] === 'r') {
- x += targetW;
- }
- if (rel[0] === 'c') {
- y += round(targetH / 2);
- }
- if (rel[1] === 'c') {
- x += round(targetW / 2);
- }
- if (rel[3] === 'b') {
- y -= h;
- }
- if (rel[4] === 'r') {
- x -= w;
- }
- if (rel[3] === 'c') {
- y -= round(h / 2);
- }
- if (rel[4] === 'c') {
- x -= round(w / 2);
- }
- return create(x, y, w, h);
- }
- /**
- * Tests various positions to get the most suitable one.
- *
- * @method findBestRelativePosition
- * @param {Rect} rect Rect to use as source.
- * @param {Rect} targetRect Rect to move relative to.
- * @param {Rect} constrainRect Rect to constrain within.
- * @param {Array} rels Array of relative positions to test against.
- */
- function findBestRelativePosition(rect, targetRect, constrainRect, rels) {
- var pos, i;
- for (i = 0; i < rels.length; i++) {
- pos = relativePosition(rect, targetRect, rels[i]);
- if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x &&
- pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) {
- return rels[i];
- }
- }
- return null;
- }
- /**
- * Inflates the rect in all directions.
- *
- * @method inflate
- * @param {Rect} rect Rect to expand.
- * @param {Number} w Relative width to expand by.
- * @param {Number} h Relative height to expand by.
- * @return {Rect} New expanded rect.
- */
- function inflate(rect, w, h) {
- return create(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2);
- }
- /**
- * Returns the intersection of the specified rectangles.
- *
- * @method intersect
- * @param {Rect} rect The first rectangle to compare.
- * @param {Rect} cropRect The second rectangle to compare.
- * @return {Rect} The intersection of the two rectangles or null if they don't intersect.
- */
- function intersect(rect, cropRect) {
- var x1, y1, x2, y2;
- x1 = max(rect.x, cropRect.x);
- y1 = max(rect.y, cropRect.y);
- x2 = min(rect.x + rect.w, cropRect.x + cropRect.w);
- y2 = min(rect.y + rect.h, cropRect.y + cropRect.h);
- if (x2 - x1 < 0 || y2 - y1 < 0) {
- return null;
- }
- return create(x1, y1, x2 - x1, y2 - y1);
- }
- /**
- * Returns a rect clamped within the specified clamp rect. This forces the
- * rect to be inside the clamp rect.
- *
- * @method clamp
- * @param {Rect} rect Rectangle to force within clamp rect.
- * @param {Rect} clampRect Rectable to force within.
- * @param {Boolean} fixedSize True/false if size should be fixed.
- * @return {Rect} Clamped rect.
- */
- function clamp(rect, clampRect, fixedSize) {
- var underflowX1, underflowY1, overflowX2, overflowY2,
- x1, y1, x2, y2, cx2, cy2;
- x1 = rect.x;
- y1 = rect.y;
- x2 = rect.x + rect.w;
- y2 = rect.y + rect.h;
- cx2 = clampRect.x + clampRect.w;
- cy2 = clampRect.y + clampRect.h;
- underflowX1 = max(0, clampRect.x - x1);
- underflowY1 = max(0, clampRect.y - y1);
- overflowX2 = max(0, x2 - cx2);
- overflowY2 = max(0, y2 - cy2);
- x1 += underflowX1;
- y1 += underflowY1;
- if (fixedSize) {
- x2 += underflowX1;
- y2 += underflowY1;
- x1 -= overflowX2;
- y1 -= overflowY2;
- }
- x2 -= overflowX2;
- y2 -= overflowY2;
- return create(x1, y1, x2 - x1, y2 - y1);
- }
- /**
- * Creates a new rectangle object.
- *
- * @method create
- * @param {Number} x Rectangle x location.
- * @param {Number} y Rectangle y location.
- * @param {Number} w Rectangle width.
- * @param {Number} h Rectangle height.
- * @return {Rect} New rectangle object.
- */
- function create(x, y, w, h) {
- return {x: x, y: y, w: w, h: h};
- }
- /**
- * Creates a new rectangle object form a clientRects object.
- *
- * @method fromClientRect
- * @param {ClientRect} clientRect DOM ClientRect object.
- * @return {Rect} New rectangle object.
- */
- function fromClientRect(clientRect) {
- return create(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
- }
- return {
- inflate: inflate,
- relativePosition: relativePosition,
- findBestRelativePosition: findBestRelativePosition,
- intersect: intersect,
- clamp: clamp,
- create: create,
- fromClientRect: fromClientRect
- };
- });
- // Included from: js/tinymce/classes/util/Promise.js
- /**
- * Promise.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * Promise polyfill under MIT license: https://github.com/taylorhakes/promise-polyfill
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /* eslint-disable */
- /* jshint ignore:start */
- /**
- * Modifed to be a feature fill and wrapped as tinymce module.
- */
- define("tinymce/util/Promise", [], function() {
- if (window.Promise) {
- return window.Promise;
- }
- // Use polyfill for setImmediate for performance gains
- var asap = Promise.immediateFn || (typeof setImmediate === 'function' && setImmediate) ||
- function(fn) { setTimeout(fn, 1); };
- // Polyfill for Function.prototype.bind
- function bind(fn, thisArg) {
- return function() {
- fn.apply(thisArg, arguments);
- };
- }
- var isArray = Array.isArray || function(value) { return Object.prototype.toString.call(value) === "[object Array]"; };
- function Promise(fn) {
- if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new');
- if (typeof fn !== 'function') throw new TypeError('not a function');
- this._state = null;
- this._value = null;
- this._deferreds = [];
- doResolve(fn, bind(resolve, this), bind(reject, this));
- }
- function handle(deferred) {
- var me = this;
- if (this._state === null) {
- this._deferreds.push(deferred);
- return;
- }
- asap(function() {
- var cb = me._state ? deferred.onFulfilled : deferred.onRejected;
- if (cb === null) {
- (me._state ? deferred.resolve : deferred.reject)(me._value);
- return;
- }
- var ret;
- try {
- ret = cb(me._value);
- }
- catch (e) {
- deferred.reject(e);
- return;
- }
- deferred.resolve(ret);
- });
- }
- function resolve(newValue) {
- try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
- if (newValue === this) throw new TypeError('A promise cannot be resolved with itself.');
- if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
- var then = newValue.then;
- if (typeof then === 'function') {
- doResolve(bind(then, newValue), bind(resolve, this), bind(reject, this));
- return;
- }
- }
- this._state = true;
- this._value = newValue;
- finale.call(this);
- } catch (e) { reject.call(this, e); }
- }
- function reject(newValue) {
- this._state = false;
- this._value = newValue;
- finale.call(this);
- }
- function finale() {
- for (var i = 0, len = this._deferreds.length; i < len; i++) {
- handle.call(this, this._deferreds[i]);
- }
- this._deferreds = null;
- }
- function Handler(onFulfilled, onRejected, resolve, reject){
- this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
- this.onRejected = typeof onRejected === 'function' ? onRejected : null;
- this.resolve = resolve;
- this.reject = reject;
- }
- /**
- * Take a potentially misbehaving resolver function and make sure
- * onFulfilled and onRejected are only called once.
- *
- * Makes no guarantees about asynchrony.
- */
- function doResolve(fn, onFulfilled, onRejected) {
- var done = false;
- try {
- fn(function (value) {
- if (done) return;
- done = true;
- onFulfilled(value);
- }, function (reason) {
- if (done) return;
- done = true;
- onRejected(reason);
- });
- } catch (ex) {
- if (done) return;
- done = true;
- onRejected(ex);
- }
- }
- Promise.prototype['catch'] = function (onRejected) {
- return this.then(null, onRejected);
- };
- Promise.prototype.then = function(onFulfilled, onRejected) {
- var me = this;
- return new Promise(function(resolve, reject) {
- handle.call(me, new Handler(onFulfilled, onRejected, resolve, reject));
- });
- };
- Promise.all = function () {
- var args = Array.prototype.slice.call(arguments.length === 1 && isArray(arguments[0]) ? arguments[0] : arguments);
- return new Promise(function (resolve, reject) {
- if (args.length === 0) return resolve([]);
- var remaining = args.length;
- function res(i, val) {
- try {
- if (val && (typeof val === 'object' || typeof val === 'function')) {
- var then = val.then;
- if (typeof then === 'function') {
- then.call(val, function (val) { res(i, val); }, reject);
- return;
- }
- }
- args[i] = val;
- if (--remaining === 0) {
- resolve(args);
- }
- } catch (ex) {
- reject(ex);
- }
- }
- for (var i = 0; i < args.length; i++) {
- res(i, args[i]);
- }
- });
- };
- Promise.resolve = function (value) {
- if (value && typeof value === 'object' && value.constructor === Promise) {
- return value;
- }
- return new Promise(function (resolve) {
- resolve(value);
- });
- };
- Promise.reject = function (value) {
- return new Promise(function (resolve, reject) {
- reject(value);
- });
- };
- Promise.race = function (values) {
- return new Promise(function (resolve, reject) {
- for(var i = 0, len = values.length; i < len; i++) {
- values[i].then(resolve, reject);
- }
- });
- };
- return Promise;
- });
- /* jshint ignore:end */
- /* eslint-enable */
- // Included from: js/tinymce/classes/util/Delay.js
- /**
- * Delay.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility class for working with delayed actions like setTimeout.
- *
- * @class tinymce.util.Delay
- */
- define("tinymce/util/Delay", [
- "tinymce/util/Promise"
- ], function(Promise) {
- var requestAnimationFramePromise;
- function requestAnimationFrame(callback, element) {
- var i, requestAnimationFrameFunc = window.requestAnimationFrame, vendors = ['ms', 'moz', 'webkit'];
- function featurefill(callback) {
- window.setTimeout(callback, 0);
- }
- for (i = 0; i < vendors.length && !requestAnimationFrameFunc; i++) {
- requestAnimationFrameFunc = window[vendors[i] + 'RequestAnimationFrame'];
- }
- if (!requestAnimationFrameFunc) {
- requestAnimationFrameFunc = featurefill;
- }
- requestAnimationFrameFunc(callback, element);
- }
- function wrappedSetTimeout(callback, time) {
- if (typeof time != 'number') {
- time = 0;
- }
- return setTimeout(callback, time);
- }
- function wrappedSetInterval(callback, time) {
- if (typeof time != 'number') {
- time = 1; // IE 8 needs it to be > 0
- }
- return setInterval(callback, time);
- }
- function wrappedClearTimeout(id) {
- return clearTimeout(id);
- }
- function wrappedClearInterval(id) {
- return clearInterval(id);
- }
- return {
- /**
- * Requests an animation frame and fallbacks to a timeout on older browsers.
- *
- * @method requestAnimationFrame
- * @param {function} callback Callback to execute when a new frame is available.
- * @param {DOMElement} element Optional element to scope it to.
- */
- requestAnimationFrame: function(callback, element) {
- if (requestAnimationFramePromise) {
- requestAnimationFramePromise.then(callback);
- return;
- }
- requestAnimationFramePromise = new Promise(function(resolve) {
- if (!element) {
- element = document.body;
- }
- requestAnimationFrame(resolve, element);
- }).then(callback);
- },
- /**
- * Sets a timer in ms and executes the specified callback when the timer runs out.
- *
- * @method setTimeout
- * @param {function} callback Callback to execute when timer runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setTimeout: wrappedSetTimeout,
- /**
- * Sets an interval timer in ms and executes the specified callback at every interval of that time.
- *
- * @method setInterval
- * @param {function} callback Callback to execute when interval time runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setInterval: wrappedSetInterval,
- /**
- * Sets an editor timeout it's similar to setTimeout except that it checks if the editor instance is
- * still alive when the callback gets executed.
- *
- * @method setEditorTimeout
- * @param {tinymce.Editor} editor Editor instance to check the removed state on.
- * @param {function} callback Callback to execute when timer runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setEditorTimeout: function(editor, callback, time) {
- return wrappedSetTimeout(function() {
- if (!editor.removed) {
- callback();
- }
- }, time);
- },
- /**
- * Sets an interval timer it's similar to setInterval except that it checks if the editor instance is
- * still alive when the callback gets executed.
- *
- * @method setEditorInterval
- * @param {function} callback Callback to execute when interval time runs out.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Number} Timeout id number.
- */
- setEditorInterval: function(editor, callback, time) {
- var timer;
- timer = wrappedSetInterval(function() {
- if (!editor.removed) {
- callback();
- } else {
- clearInterval(timer);
- }
- }, time);
- return timer;
- },
- /**
- * Creates throttled callback function that only gets executed once within the specified time.
- *
- * @method throttle
- * @param {function} callback Callback to execute when timer finishes.
- * @param {Number} time Optional time to wait before the callback is executed, defaults to 0.
- * @return {Function} Throttled function callback.
- */
- throttle: function(callback, time) {
- var timer, func;
- func = function() {
- var args = arguments;
- clearTimeout(timer);
- timer = wrappedSetTimeout(function() {
- callback.apply(this, args);
- }, time);
- };
- func.stop = function() {
- clearTimeout(timer);
- };
- return func;
- },
- /**
- * Clears an interval timer so it won't execute.
- *
- * @method clearInterval
- * @param {Number} Interval timer id number.
- */
- clearInterval: wrappedClearInterval,
- /**
- * Clears an timeout timer so it won't execute.
- *
- * @method clearTimeout
- * @param {Number} Timeout timer id number.
- */
- clearTimeout: wrappedClearTimeout
- };
- });
- // Included from: js/tinymce/classes/Env.js
- /**
- * Env.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class contains various environment constants like browser versions etc.
- * Normally you don't want to sniff specific browser versions but sometimes you have
- * to when it's impossible to feature detect. So use this with care.
- *
- * @class tinymce.Env
- * @static
- */
- define("tinymce/Env", [], function() {
- var nav = navigator, userAgent = nav.userAgent;
- var opera, webkit, ie, ie11, ie12, gecko, mac, iDevice, android, fileApi, phone, tablet, windowsPhone;
- function matchMediaQuery(query) {
- return "matchMedia" in window ? matchMedia(query).matches : false;
- }
- opera = window.opera && window.opera.buildNumber;
- android = /Android/.test(userAgent);
- webkit = /WebKit/.test(userAgent);
- ie = !webkit && !opera && (/MSIE/gi).test(userAgent) && (/Explorer/gi).test(nav.appName);
- ie = ie && /MSIE (\w+)\./.exec(userAgent)[1];
- ie11 = userAgent.indexOf('Trident/') != -1 && (userAgent.indexOf('rv:') != -1 || nav.appName.indexOf('Netscape') != -1) ? 11 : false;
- ie12 = (userAgent.indexOf('Edge/') != -1 && !ie && !ie11) ? 12 : false;
- ie = ie || ie11 || ie12;
- gecko = !webkit && !ie11 && /Gecko/.test(userAgent);
- mac = userAgent.indexOf('Mac') != -1;
- iDevice = /(iPad|iPhone)/.test(userAgent);
- fileApi = "FormData" in window && "FileReader" in window && "URL" in window && !!URL.createObjectURL;
- phone = matchMediaQuery("only screen and (max-device-width: 480px)") && (android || iDevice);
- tablet = matchMediaQuery("only screen and (min-width: 800px)") && (android || iDevice);
- windowsPhone = userAgent.indexOf('Windows Phone') != -1;
- if (ie12) {
- webkit = false;
- }
- // Is a iPad/iPhone and not on iOS5 sniff the WebKit version since older iOS WebKit versions
- // says it has contentEditable support but there is no visible caret.
- var contentEditable = !iDevice || fileApi || userAgent.match(/AppleWebKit\/(\d*)/)[1] >= 534;
- return {
- /**
- * Constant that is true if the browser is Opera.
- *
- * @property opera
- * @type Boolean
- * @final
- */
- opera: opera,
- /**
- * Constant that is true if the browser is WebKit (Safari/Chrome).
- *
- * @property webKit
- * @type Boolean
- * @final
- */
- webkit: webkit,
- /**
- * Constant that is more than zero if the browser is IE.
- *
- * @property ie
- * @type Boolean
- * @final
- */
- ie: ie,
- /**
- * Constant that is true if the browser is Gecko.
- *
- * @property gecko
- * @type Boolean
- * @final
- */
- gecko: gecko,
- /**
- * Constant that is true if the os is Mac OS.
- *
- * @property mac
- * @type Boolean
- * @final
- */
- mac: mac,
- /**
- * Constant that is true if the os is iOS.
- *
- * @property iOS
- * @type Boolean
- * @final
- */
- iOS: iDevice,
- /**
- * Constant that is true if the os is android.
- *
- * @property android
- * @type Boolean
- * @final
- */
- android: android,
- /**
- * Constant that is true if the browser supports editing.
- *
- * @property contentEditable
- * @type Boolean
- * @final
- */
- contentEditable: contentEditable,
- /**
- * Transparent image data url.
- *
- * @property transparentSrc
- * @type Boolean
- * @final
- */
- transparentSrc: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
- /**
- * Returns true/false if the browser can or can't place the caret after a inline block like an image.
- *
- * @property noCaretAfter
- * @type Boolean
- * @final
- */
- caretAfter: ie != 8,
- /**
- * Constant that is true if the browser supports native DOM Ranges. IE 9+.
- *
- * @property range
- * @type Boolean
- */
- range: window.getSelection && "Range" in window,
- /**
- * Returns the IE document mode for non IE browsers this will fake IE 10.
- *
- * @property documentMode
- * @type Number
- */
- documentMode: ie && !ie12 ? (document.documentMode || 7) : 10,
- /**
- * Constant that is true if the browser has a modern file api.
- *
- * @property fileApi
- * @type Boolean
- */
- fileApi: fileApi,
- /**
- * Constant that is true if the browser supports contentEditable=false regions.
- *
- * @property ceFalse
- * @type Boolean
- */
- ceFalse: (ie === false || ie > 8),
- desktop: !phone && !tablet,
- windowsPhone: windowsPhone
- };
- });
- // Included from: js/tinymce/classes/dom/EventUtils.js
- /**
- * EventUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*jshint loopfunc:true*/
- /*eslint no-loop-func:0 */
- /**
- * This class wraps the browsers native event logic with more convenient methods.
- *
- * @class tinymce.dom.EventUtils
- */
- define("tinymce/dom/EventUtils", [
- "tinymce/util/Delay",
- "tinymce/Env"
- ], function(Delay, Env) {
- "use strict";
- var eventExpandoPrefix = "mce-data-";
- var mouseEventRe = /^(?:mouse|contextmenu)|click/;
- var deprecated = {
- keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1,
- webkitMovementX: 1, webkitMovementY: 1, keyIdentifier: 1
- };
- /**
- * Binds a native event to a callback on the speified target.
- */
- function addEvent(target, name, callback, capture) {
- if (target.addEventListener) {
- target.addEventListener(name, callback, capture || false);
- } else if (target.attachEvent) {
- target.attachEvent('on' + name, callback);
- }
- }
- /**
- * Unbinds a native event callback on the specified target.
- */
- function removeEvent(target, name, callback, capture) {
- if (target.removeEventListener) {
- target.removeEventListener(name, callback, capture || false);
- } else if (target.detachEvent) {
- target.detachEvent('on' + name, callback);
- }
- }
- /**
- * Gets the event target based on shadow dom properties like path and deepPath.
- */
- function getTargetFromShadowDom(event, defaultTarget) {
- var path, target = defaultTarget;
- // When target element is inside Shadow DOM we need to take first element from path
- // otherwise we'll get Shadow Root parent, not actual target element
- // Normalize target for WebComponents v0 implementation (in Chrome)
- path = event.path;
- if (path && path.length > 0) {
- target = path[0];
- }
- // Normalize target for WebComponents v1 implementation (standard)
- if (event.deepPath) {
- path = event.deepPath();
- if (path && path.length > 0) {
- target = path[0];
- }
- }
- return target;
- }
- /**
- * Normalizes a native event object or just adds the event specific methods on a custom event.
- */
- function fix(originalEvent, data) {
- var name, event = data || {}, undef;
- // Dummy function that gets replaced on the delegation state functions
- function returnFalse() {
- return false;
- }
- // Dummy function that gets replaced on the delegation state functions
- function returnTrue() {
- return true;
- }
- // Copy all properties from the original event
- for (name in originalEvent) {
- // layerX/layerY is deprecated in Chrome and produces a warning
- if (!deprecated[name]) {
- event[name] = originalEvent[name];
- }
- }
- // Normalize target IE uses srcElement
- if (!event.target) {
- event.target = event.srcElement || document;
- }
- // Experimental shadow dom support
- if (Env.experimentalShadowDom) {
- event.target = getTargetFromShadowDom(originalEvent, event.target);
- }
- // Calculate pageX/Y if missing and clientX/Y available
- if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
- var eventDoc = event.target.ownerDocument || document;
- var doc = eventDoc.documentElement;
- var body = eventDoc.body;
- event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
- (doc && doc.clientLeft || body && body.clientLeft || 0);
- event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) -
- (doc && doc.clientTop || body && body.clientTop || 0);
- }
- // Add preventDefault method
- event.preventDefault = function() {
- event.isDefaultPrevented = returnTrue;
- // Execute preventDefault on the original event object
- if (originalEvent) {
- if (originalEvent.preventDefault) {
- originalEvent.preventDefault();
- } else {
- originalEvent.returnValue = false; // IE
- }
- }
- };
- // Add stopPropagation
- event.stopPropagation = function() {
- event.isPropagationStopped = returnTrue;
- // Execute stopPropagation on the original event object
- if (originalEvent) {
- if (originalEvent.stopPropagation) {
- originalEvent.stopPropagation();
- } else {
- originalEvent.cancelBubble = true; // IE
- }
- }
- };
- // Add stopImmediatePropagation
- event.stopImmediatePropagation = function() {
- event.isImmediatePropagationStopped = returnTrue;
- event.stopPropagation();
- };
- // Add event delegation states
- if (!event.isDefaultPrevented) {
- event.isDefaultPrevented = returnFalse;
- event.isPropagationStopped = returnFalse;
- event.isImmediatePropagationStopped = returnFalse;
- }
- // Add missing metaKey for IE 8
- if (typeof event.metaKey == 'undefined') {
- event.metaKey = false;
- }
- return event;
- }
- /**
- * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
- * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
- */
- function bindOnReady(win, callback, eventUtils) {
- var doc = win.document, event = {type: 'ready'};
- if (eventUtils.domLoaded) {
- callback(event);
- return;
- }
- // Gets called when the DOM is ready
- function readyHandler() {
- if (!eventUtils.domLoaded) {
- eventUtils.domLoaded = true;
- callback(event);
- }
- }
- function waitForDomLoaded() {
- // Check complete or interactive state if there is a body
- // element on some iframes IE 8 will produce a null body
- if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
- removeEvent(doc, "readystatechange", waitForDomLoaded);
- readyHandler();
- }
- }
- function tryScroll() {
- try {
- // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
- // http://javascript.nwbox.com/IEContentLoaded/
- doc.documentElement.doScroll("left");
- } catch (ex) {
- Delay.setTimeout(tryScroll);
- return;
- }
- readyHandler();
- }
- // Use W3C method
- if (doc.addEventListener) {
- if (doc.readyState === "complete") {
- readyHandler();
- } else {
- addEvent(win, 'DOMContentLoaded', readyHandler);
- }
- } else {
- // Use IE method
- addEvent(doc, "readystatechange", waitForDomLoaded);
- // Wait until we can scroll, when we can the DOM is initialized
- if (doc.documentElement.doScroll && win.self === win.top) {
- tryScroll();
- }
- }
- // Fallback if any of the above methods should fail for some odd reason
- addEvent(win, 'load', readyHandler);
- }
- /**
- * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
- */
- function EventUtils() {
- var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
- expando = eventExpandoPrefix + (+new Date()).toString(32);
- hasMouseEnterLeave = "onmouseenter" in document.documentElement;
- hasFocusIn = "onfocusin" in document.documentElement;
- mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
- count = 1;
- // State if the DOMContentLoaded was executed or not
- self.domLoaded = false;
- self.events = events;
- /**
- * Executes all event handler callbacks for a specific event.
- *
- * @private
- * @param {Event} evt Event object.
- * @param {String} id Expando id value to look for.
- */
- function executeHandlers(evt, id) {
- var callbackList, i, l, callback, container = events[id];
- callbackList = container && container[evt.type];
- if (callbackList) {
- for (i = 0, l = callbackList.length; i < l; i++) {
- callback = callbackList[i];
- // Check if callback exists might be removed if a unbind is called inside the callback
- if (callback && callback.func.call(callback.scope, evt) === false) {
- evt.preventDefault();
- }
- // Should we stop propagation to immediate listeners
- if (evt.isImmediatePropagationStopped()) {
- return;
- }
- }
- }
- }
- /**
- * Binds a callback to an event on the specified target.
- *
- * @method bind
- * @param {Object} target Target node/window or custom object.
- * @param {String} names Name of the event to bind.
- * @param {function} callback Callback function to execute when the event occurs.
- * @param {Object} scope Scope to call the callback function on, defaults to target.
- * @return {function} Callback function that got bound.
- */
- self.bind = function(target, names, callback, scope) {
- var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
- // Native event handler function patches the event and executes the callbacks for the expando
- function defaultNativeHandler(evt) {
- executeHandlers(fix(evt || win.event), id);
- }
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return;
- }
- // Create or get events id for the target
- if (!target[expando]) {
- id = count++;
- target[expando] = id;
- events[id] = {};
- } else {
- id = target[expando];
- }
- // Setup the specified scope or use the target as a default
- scope = scope || target;
- // Split names and bind each event, enables you to bind multiple events with one call
- names = names.split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- nativeHandler = defaultNativeHandler;
- fakeName = capture = false;
- // Use ready instead of DOMContentLoaded
- if (name === "DOMContentLoaded") {
- name = "ready";
- }
- // DOM is already ready
- if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
- callback.call(scope, fix({type: name}));
- continue;
- }
- // Handle mouseenter/mouseleaver
- if (!hasMouseEnterLeave) {
- fakeName = mouseEnterLeave[name];
- if (fakeName) {
- nativeHandler = function(evt) {
- var current, related;
- current = evt.currentTarget;
- related = evt.relatedTarget;
- // Check if related is inside the current target if it's not then the event should
- // be ignored since it's a mouseover/mouseout inside the element
- if (related && current.contains) {
- // Use contains for performance
- related = current.contains(related);
- } else {
- while (related && related !== current) {
- related = related.parentNode;
- }
- }
- // Fire fake event
- if (!related) {
- evt = fix(evt || win.event);
- evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
- evt.target = current;
- executeHandlers(evt, id);
- }
- };
- }
- }
- // Fake bubbling of focusin/focusout
- if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
- capture = true;
- fakeName = name === "focusin" ? "focus" : "blur";
- nativeHandler = function(evt) {
- evt = fix(evt || win.event);
- evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
- executeHandlers(evt, id);
- };
- }
- // Setup callback list and bind native event
- callbackList = events[id][name];
- if (!callbackList) {
- events[id][name] = callbackList = [{func: callback, scope: scope}];
- callbackList.fakeName = fakeName;
- callbackList.capture = capture;
- //callbackList.callback = callback;
- // Add the nativeHandler to the callback list so that we can later unbind it
- callbackList.nativeHandler = nativeHandler;
- // Check if the target has native events support
- if (name === "ready") {
- bindOnReady(target, nativeHandler, self);
- } else {
- addEvent(target, fakeName || name, nativeHandler, capture);
- }
- } else {
- if (name === "ready" && self.domLoaded) {
- callback({type: name});
- } else {
- // If it already has an native handler then just push the callback
- callbackList.push({func: callback, scope: scope});
- }
- }
- }
- target = callbackList = 0; // Clean memory for IE
- return callback;
- };
- /**
- * Unbinds the specified event by name, name and callback or all events on the target.
- *
- * @method unbind
- * @param {Object} target Target node/window or custom object.
- * @param {String} names Optional event name to unbind.
- * @param {function} callback Optional callback function to unbind.
- * @return {EventUtils} Event utils instance.
- */
- self.unbind = function(target, names, callback) {
- var id, callbackList, i, ci, name, eventMap;
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
- // Unbind event or events if the target has the expando
- id = target[expando];
- if (id) {
- eventMap = events[id];
- // Specific callback
- if (names) {
- names = names.split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- callbackList = eventMap[name];
- // Unbind the event if it exists in the map
- if (callbackList) {
- // Remove specified callback
- if (callback) {
- ci = callbackList.length;
- while (ci--) {
- if (callbackList[ci].func === callback) {
- var nativeHandler = callbackList.nativeHandler;
- var fakeName = callbackList.fakeName, capture = callbackList.capture;
- // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
- callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
- callbackList.nativeHandler = nativeHandler;
- callbackList.fakeName = fakeName;
- callbackList.capture = capture;
- eventMap[name] = callbackList;
- }
- }
- }
- // Remove all callbacks if there isn't a specified callback or there is no callbacks left
- if (!callback || callbackList.length === 0) {
- delete eventMap[name];
- removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
- }
- }
- }
- } else {
- // All events for a specific element
- for (name in eventMap) {
- callbackList = eventMap[name];
- removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
- }
- eventMap = {};
- }
- // Check if object is empty, if it isn't then we won't remove the expando map
- for (name in eventMap) {
- return self;
- }
- // Delete event object
- delete events[id];
- // Remove expando from target
- try {
- // IE will fail here since it can't delete properties from window
- delete target[expando];
- } catch (ex) {
- // IE will set it to null
- target[expando] = null;
- }
- }
- return self;
- };
- /**
- * Fires the specified event on the specified target.
- *
- * @method fire
- * @param {Object} target Target node/window or custom object.
- * @param {String} name Event name to fire.
- * @param {Object} args Optional arguments to send to the observers.
- * @return {EventUtils} Event utils instance.
- */
- self.fire = function(target, name, args) {
- var id;
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
- // Build event object by patching the args
- args = fix(null, args);
- args.type = name;
- args.target = target;
- do {
- // Found an expando that means there is listeners to execute
- id = target[expando];
- if (id) {
- executeHandlers(args, id);
- }
- // Walk up the DOM
- target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
- } while (target && !args.isPropagationStopped());
- return self;
- };
- /**
- * Removes all bound event listeners for the specified target. This will also remove any bound
- * listeners to child nodes within that target.
- *
- * @method clean
- * @param {Object} target Target node/window object.
- * @return {EventUtils} Event utils instance.
- */
- self.clean = function(target) {
- var i, children, unbind = self.unbind;
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
- // Unbind any element on the specified target
- if (target[expando]) {
- unbind(target);
- }
- // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
- if (!target.getElementsByTagName) {
- target = target.document;
- }
- // Remove events from each child element
- if (target && target.getElementsByTagName) {
- unbind(target);
- children = target.getElementsByTagName('*');
- i = children.length;
- while (i--) {
- target = children[i];
- if (target[expando]) {
- unbind(target);
- }
- }
- }
- return self;
- };
- /**
- * Destroys the event object. Call this on IE to remove memory leaks.
- */
- self.destroy = function() {
- events = {};
- };
- // Legacy function for canceling events
- self.cancel = function(e) {
- if (e) {
- e.preventDefault();
- e.stopImmediatePropagation();
- }
- return false;
- };
- }
- EventUtils.Event = new EventUtils();
- EventUtils.Event.bind(window, 'ready', function() {});
- return EventUtils;
- });
- // Included from: js/tinymce/classes/dom/Sizzle.js
- /**
- * Sizzle.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- *
- * @ignore-file
- */
- /*jshint bitwise:false, expr:true, noempty:false, sub:true, eqnull:true, latedef:false, maxlen:255 */
- /*eslint-disable */
- /**
- * Sizzle CSS Selector Engine v@VERSION
- * http://sizzlejs.com/
- *
- * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
- * Released under the MIT license
- * http://jquery.org/license
- *
- * Date: @DATE
- */
- define("tinymce/dom/Sizzle", [], function() {
- var i,
- support,
- Expr,
- getText,
- isXML,
- tokenize,
- compile,
- select,
- outermostContext,
- sortInput,
- hasDuplicate,
- // Local document vars
- setDocument,
- document,
- docElem,
- documentIsHTML,
- rbuggyQSA,
- rbuggyMatches,
- matches,
- contains,
- // Instance-specific data
- expando = "sizzle" + -(new Date()),
- preferredDoc = window.document,
- dirruns = 0,
- done = 0,
- classCache = createCache(),
- tokenCache = createCache(),
- compilerCache = createCache(),
- sortOrder = function( a, b ) {
- if ( a === b ) {
- hasDuplicate = true;
- }
- return 0;
- },
- // General-purpose constants
- strundefined = typeof undefined,
- MAX_NEGATIVE = 1 << 31,
- // Instance methods
- hasOwn = ({}).hasOwnProperty,
- arr = [],
- pop = arr.pop,
- push_native = arr.push,
- push = arr.push,
- slice = arr.slice,
- // Use a stripped-down indexOf if we can't use a native one
- indexOf = arr.indexOf || function( elem ) {
- var i = 0,
- len = this.length;
- for ( ; i < len; i++ ) {
- if ( this[i] === elem ) {
- return i;
- }
- }
- return -1;
- },
- booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
- // Regular expressions
- // http://www.w3.org/TR/css3-selectors/#whitespace
- whitespace = "[\\x20\\t\\r\\n\\f]",
- // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
- identifier = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
- // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
- attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
- // Operator (capture 2)
- "*([*^$|!~]?=)" + whitespace +
- // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
- "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
- "*\\]",
- pseudos = ":(" + identifier + ")(?:\\((" +
- // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
- // 1. quoted (capture 3; capture 4 or capture 5)
- "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
- // 2. simple (capture 6)
- "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
- // 3. anything else (capture 2)
- ".*" +
- ")\\)|)",
- // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
- rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
- rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
- rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
- rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
- rpseudo = new RegExp( pseudos ),
- ridentifier = new RegExp( "^" + identifier + "$" ),
- matchExpr = {
- "ID": new RegExp( "^#(" + identifier + ")" ),
- "CLASS": new RegExp( "^\\.(" + identifier + ")" ),
- "TAG": new RegExp( "^(" + identifier + "|[*])" ),
- "ATTR": new RegExp( "^" + attributes ),
- "PSEUDO": new RegExp( "^" + pseudos ),
- "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
- "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
- "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
- "bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
- // For use in libraries implementing .is()
- // We use this for POS matching in `select`
- "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
- whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
- },
- rinputs = /^(?:input|select|textarea|button)$/i,
- rheader = /^h\d$/i,
- rnative = /^[^{]+\{\s*\[native \w/,
- // Easily-parseable/retrievable ID or TAG or CLASS selectors
- rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
- rsibling = /[+~]/,
- rescape = /'|\\/g,
- // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
- runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
- funescape = function( _, escaped, escapedWhitespace ) {
- var high = "0x" + escaped - 0x10000;
- // NaN means non-codepoint
- // Support: Firefox<24
- // Workaround erroneous numeric interpretation of +"0x"
- return high !== high || escapedWhitespace ?
- escaped :
- high < 0 ?
- // BMP codepoint
- String.fromCharCode( high + 0x10000 ) :
- // Supplemental Plane codepoint (surrogate pair)
- String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
- };
- // Optimize for push.apply( _, NodeList )
- try {
- push.apply(
- (arr = slice.call( preferredDoc.childNodes )),
- preferredDoc.childNodes
- );
- // Support: Android<4.0
- // Detect silently failing push.apply
- arr[ preferredDoc.childNodes.length ].nodeType;
- } catch ( e ) {
- push = { apply: arr.length ?
- // Leverage slice if possible
- function( target, els ) {
- push_native.apply( target, slice.call(els) );
- } :
- // Support: IE<9
- // Otherwise append directly
- function( target, els ) {
- var j = target.length,
- i = 0;
- // Can't trust NodeList.length
- while ( (target[j++] = els[i++]) ) {}
- target.length = j - 1;
- }
- };
- }
- function Sizzle( selector, context, results, seed ) {
- var match, elem, m, nodeType,
- // QSA vars
- i, groups, old, nid, newContext, newSelector;
- if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
- setDocument( context );
- }
- context = context || document;
- results = results || [];
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
- if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
- return [];
- }
- if ( documentIsHTML && !seed ) {
- // Shortcuts
- if ( (match = rquickExpr.exec( selector )) ) {
- // Speed-up: Sizzle("#ID")
- if ( (m = match[1]) ) {
- if ( nodeType === 9 ) {
- elem = context.getElementById( m );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document (jQuery #6963)
- if ( elem && elem.parentNode ) {
- // Handle the case where IE, Opera, and Webkit return items
- // by name instead of ID
- if ( elem.id === m ) {
- results.push( elem );
- return results;
- }
- } else {
- return results;
- }
- } else {
- // Context is not a document
- if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
- contains( context, elem ) && elem.id === m ) {
- results.push( elem );
- return results;
- }
- }
- // Speed-up: Sizzle("TAG")
- } else if ( match[2] ) {
- push.apply( results, context.getElementsByTagName( selector ) );
- return results;
- // Speed-up: Sizzle(".CLASS")
- } else if ( (m = match[3]) && support.getElementsByClassName ) {
- push.apply( results, context.getElementsByClassName( m ) );
- return results;
- }
- }
- // QSA path
- if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
- nid = old = expando;
- newContext = context;
- newSelector = nodeType === 9 && selector;
- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- groups = tokenize( selector );
- if ( (old = context.getAttribute("id")) ) {
- nid = old.replace( rescape, "\\$&" );
- } else {
- context.setAttribute( "id", nid );
- }
- nid = "[id='" + nid + "'] ";
- i = groups.length;
- while ( i-- ) {
- groups[i] = nid + toSelector( groups[i] );
- }
- newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
- newSelector = groups.join(",");
- }
- if ( newSelector ) {
- try {
- push.apply( results,
- newContext.querySelectorAll( newSelector )
- );
- return results;
- } catch(qsaError) {
- } finally {
- if ( !old ) {
- context.removeAttribute("id");
- }
- }
- }
- }
- }
- // All others
- return select( selector.replace( rtrim, "$1" ), context, results, seed );
- }
- /**
- * Create key-value caches of limited size
- * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
- * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
- * deleting the oldest entry
- */
- function createCache() {
- var keys = [];
- function cache( key, value ) {
- // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
- if ( keys.push( key + " " ) > Expr.cacheLength ) {
- // Only keep the most recent entries
- delete cache[ keys.shift() ];
- }
- return (cache[ key + " " ] = value);
- }
- return cache;
- }
- /**
- * Mark a function for special use by Sizzle
- * @param {Function} fn The function to mark
- */
- function markFunction( fn ) {
- fn[ expando ] = true;
- return fn;
- }
- /**
- * Support testing using an element
- * @param {Function} fn Passed the created div and expects a boolean result
- */
- function assert( fn ) {
- var div = document.createElement("div");
- try {
- return !!fn( div );
- } catch (e) {
- return false;
- } finally {
- // Remove from its parent by default
- if ( div.parentNode ) {
- div.parentNode.removeChild( div );
- }
- // release memory in IE
- div = null;
- }
- }
- /**
- * Adds the same handler for all of the specified attrs
- * @param {String} attrs Pipe-separated list of attributes
- * @param {Function} handler The method that will be applied
- */
- function addHandle( attrs, handler ) {
- var arr = attrs.split("|"),
- i = attrs.length;
- while ( i-- ) {
- Expr.attrHandle[ arr[i] ] = handler;
- }
- }
- /**
- * Checks document order of two siblings
- * @param {Element} a
- * @param {Element} b
- * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
- */
- function siblingCheck( a, b ) {
- var cur = b && a,
- diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
- ( ~b.sourceIndex || MAX_NEGATIVE ) -
- ( ~a.sourceIndex || MAX_NEGATIVE );
- // Use IE sourceIndex if available on both nodes
- if ( diff ) {
- return diff;
- }
- // Check if b follows a
- if ( cur ) {
- while ( (cur = cur.nextSibling) ) {
- if ( cur === b ) {
- return -1;
- }
- }
- }
- return a ? 1 : -1;
- }
- /**
- * Returns a function to use in pseudos for input types
- * @param {String} type
- */
- function createInputPseudo( type ) {
- return function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && elem.type === type;
- };
- }
- /**
- * Returns a function to use in pseudos for buttons
- * @param {String} type
- */
- function createButtonPseudo( type ) {
- return function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return (name === "input" || name === "button") && elem.type === type;
- };
- }
- /**
- * Returns a function to use in pseudos for positionals
- * @param {Function} fn
- */
- function createPositionalPseudo( fn ) {
- return markFunction(function( argument ) {
- argument = +argument;
- return markFunction(function( seed, matches ) {
- var j,
- matchIndexes = fn( [], seed.length, argument ),
- i = matchIndexes.length;
- // Match elements found at the specified indexes
- while ( i-- ) {
- if ( seed[ (j = matchIndexes[i]) ] ) {
- seed[j] = !(matches[j] = seed[j]);
- }
- }
- });
- });
- }
- /**
- * Checks a node for validity as a Sizzle context
- * @param {Element|Object=} context
- * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
- */
- function testContext( context ) {
- return context && typeof context.getElementsByTagName !== strundefined && context;
- }
- // Expose support vars for convenience
- support = Sizzle.support = {};
- /**
- * Detects XML nodes
- * @param {Element|Object} elem An element or a document
- * @returns {Boolean} True iff elem is a non-HTML XML node
- */
- isXML = Sizzle.isXML = function( elem ) {
- // documentElement is verified for cases where it doesn't yet exist
- // (such as loading iframes in IE - #4833)
- var documentElement = elem && (elem.ownerDocument || elem).documentElement;
- return documentElement ? documentElement.nodeName !== "HTML" : false;
- };
- /**
- * Sets document-related variables once based on the current document
- * @param {Element|Object} [doc] An element or document object to use to set the document
- * @returns {Object} Returns the current document
- */
- setDocument = Sizzle.setDocument = function( node ) {
- var hasCompare,
- doc = node ? node.ownerDocument || node : preferredDoc,
- parent = doc.defaultView;
- function getTop(win) {
- // Edge throws a lovely Object expected if you try to get top on a detached reference see #2642
- try {
- return win.top;
- } catch (ex) {
- // Ignore
- }
- return null;
- }
- // If no document and documentElement is available, return
- if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
- return document;
- }
- // Set our document
- document = doc;
- docElem = doc.documentElement;
- // Support tests
- documentIsHTML = !isXML( doc );
- // Support: IE>8
- // If iframe document is assigned to "document" variable and if iframe has been reloaded,
- // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
- // IE6-8 do not support the defaultView property so parent will be undefined
- if ( parent && parent !== getTop(parent) ) {
- // IE11 does not have attachEvent, so all must suffer
- if ( parent.addEventListener ) {
- parent.addEventListener( "unload", function() {
- setDocument();
- }, false );
- } else if ( parent.attachEvent ) {
- parent.attachEvent( "onunload", function() {
- setDocument();
- });
- }
- }
- /* Attributes
- ---------------------------------------------------------------------- */
- // Support: IE<8
- // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
- support.attributes = assert(function( div ) {
- div.className = "i";
- return !div.getAttribute("className");
- });
- /* getElement(s)By*
- ---------------------------------------------------------------------- */
- // Check if getElementsByTagName("*") returns only elements
- support.getElementsByTagName = assert(function( div ) {
- div.appendChild( doc.createComment("") );
- return !div.getElementsByTagName("*").length;
- });
- // Support: IE<9
- support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
- // Support: IE<10
- // Check if getElementById returns elements by name
- // The broken getElementById methods don't pick up programatically-set names,
- // so use a roundabout getElementsByName test
- support.getById = assert(function( div ) {
- docElem.appendChild( div ).id = expando;
- return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
- });
- // ID find and filter
- if ( support.getById ) {
- Expr.find["ID"] = function( id, context ) {
- if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
- var m = context.getElementById( id );
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [ m ] : [];
- }
- };
- Expr.filter["ID"] = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- return elem.getAttribute("id") === attrId;
- };
- };
- } else {
- // Support: IE6/7
- // getElementById is not reliable as a find shortcut
- delete Expr.find["ID"];
- Expr.filter["ID"] = function( id ) {
- var attrId = id.replace( runescape, funescape );
- return function( elem ) {
- var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
- return node && node.value === attrId;
- };
- };
- }
- // Tag
- Expr.find["TAG"] = support.getElementsByTagName ?
- function( tag, context ) {
- if ( typeof context.getElementsByTagName !== strundefined ) {
- return context.getElementsByTagName( tag );
- }
- } :
- function( tag, context ) {
- var elem,
- tmp = [],
- i = 0,
- results = context.getElementsByTagName( tag );
- // Filter out possible comments
- if ( tag === "*" ) {
- while ( (elem = results[i++]) ) {
- if ( elem.nodeType === 1 ) {
- tmp.push( elem );
- }
- }
- return tmp;
- }
- return results;
- };
- // Class
- Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
- if ( documentIsHTML ) {
- return context.getElementsByClassName( className );
- }
- };
- /* QSA/matchesSelector
- ---------------------------------------------------------------------- */
- // QSA and matchesSelector support
- // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
- rbuggyMatches = [];
- // qSa(:focus) reports false when true (Chrome 21)
- // We allow this because of a bug in IE8/9 that throws an error
- // whenever `document.activeElement` is accessed on an iframe
- // So, we allow :focus to pass through QSA all the time to avoid the IE error
- // See http://bugs.jquery.com/ticket/13378
- rbuggyQSA = [];
- if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
- // Build QSA regex
- // Regex strategy adopted from Diego Perini
- assert(function( div ) {
- // Select is set to empty string on purpose
- // This is to test IE's treatment of not explicitly
- // setting a boolean content attribute,
- // since its presence should be enough
- // http://bugs.jquery.com/ticket/12359
- div.innerHTML = "<select msallowcapture=''><option selected=''></option></select>";
- // Support: IE8, Opera 11-12.16
- // Nothing should be selected when empty strings follow ^= or $= or *=
- // The test attribute must be unknown in Opera but "safe" for WinRT
- // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
- if ( div.querySelectorAll("[msallowcapture^='']").length ) {
- rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
- }
- // Support: IE8
- // Boolean attributes and "value" are not treated correctly
- if ( !div.querySelectorAll("[selected]").length ) {
- rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
- }
- // Webkit/Opera - :checked should return selected option elements
- // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- // IE8 throws error here and will not see later tests
- if ( !div.querySelectorAll(":checked").length ) {
- rbuggyQSA.push(":checked");
- }
- });
- assert(function( div ) {
- // Support: Windows 8 Native Apps
- // The type and name attributes are restricted during .innerHTML assignment
- var input = doc.createElement("input");
- input.setAttribute( "type", "hidden" );
- div.appendChild( input ).setAttribute( "name", "D" );
- // Support: IE8
- // Enforce case-sensitivity of name attribute
- if ( div.querySelectorAll("[name=d]").length ) {
- rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
- }
- // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
- // IE8 throws error here and will not see later tests
- if ( !div.querySelectorAll(":enabled").length ) {
- rbuggyQSA.push( ":enabled", ":disabled" );
- }
- // Opera 10-11 does not throw on post-comma invalid pseudos
- div.querySelectorAll("*,:x");
- rbuggyQSA.push(",.*:");
- });
- }
- if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
- docElem.webkitMatchesSelector ||
- docElem.mozMatchesSelector ||
- docElem.oMatchesSelector ||
- docElem.msMatchesSelector) )) ) {
- assert(function( div ) {
- // Check to see if it's possible to do matchesSelector
- // on a disconnected node (IE 9)
- support.disconnectedMatch = matches.call( div, "div" );
- // This should fail with an exception
- // Gecko does not error, returns false instead
- matches.call( div, "[s!='']:x" );
- rbuggyMatches.push( "!=", pseudos );
- });
- }
- rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
- rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
- /* Contains
- ---------------------------------------------------------------------- */
- hasCompare = rnative.test( docElem.compareDocumentPosition );
- // Element contains another
- // Purposefully does not implement inclusive descendent
- // As in, an element does not contain itself
- contains = hasCompare || rnative.test( docElem.contains ) ?
- function( a, b ) {
- var adown = a.nodeType === 9 ? a.documentElement : a,
- bup = b && b.parentNode;
- return a === bup || !!( bup && bup.nodeType === 1 && (
- adown.contains ?
- adown.contains( bup ) :
- a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
- ));
- } :
- function( a, b ) {
- if ( b ) {
- while ( (b = b.parentNode) ) {
- if ( b === a ) {
- return true;
- }
- }
- }
- return false;
- };
- /* Sorting
- ---------------------------------------------------------------------- */
- // Document order sorting
- sortOrder = hasCompare ?
- function( a, b ) {
- // Flag for duplicate removal
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
- // Sort on method existence if only one input has compareDocumentPosition
- var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
- if ( compare ) {
- return compare;
- }
- // Calculate position if both inputs belong to the same document
- compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
- a.compareDocumentPosition( b ) :
- // Otherwise we know they are disconnected
- 1;
- // Disconnected nodes
- if ( compare & 1 ||
- (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
- // Choose the first element that is related to our preferred document
- if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
- return -1;
- }
- if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
- return 1;
- }
- // Maintain original order
- return sortInput ?
- ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
- 0;
- }
- return compare & 4 ? -1 : 1;
- } :
- function( a, b ) {
- // Exit early if the nodes are identical
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
- var cur,
- i = 0,
- aup = a.parentNode,
- bup = b.parentNode,
- ap = [ a ],
- bp = [ b ];
- // Parentless nodes are either documents or disconnected
- if ( !aup || !bup ) {
- return a === doc ? -1 :
- b === doc ? 1 :
- aup ? -1 :
- bup ? 1 :
- sortInput ?
- ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
- 0;
- // If the nodes are siblings, we can do a quick check
- } else if ( aup === bup ) {
- return siblingCheck( a, b );
- }
- // Otherwise we need full lists of their ancestors for comparison
- cur = a;
- while ( (cur = cur.parentNode) ) {
- ap.unshift( cur );
- }
- cur = b;
- while ( (cur = cur.parentNode) ) {
- bp.unshift( cur );
- }
- // Walk down the tree looking for a discrepancy
- while ( ap[i] === bp[i] ) {
- i++;
- }
- return i ?
- // Do a sibling check if the nodes have a common ancestor
- siblingCheck( ap[i], bp[i] ) :
- // Otherwise nodes in our document sort first
- ap[i] === preferredDoc ? -1 :
- bp[i] === preferredDoc ? 1 :
- 0;
- };
- return doc;
- };
- Sizzle.matches = function( expr, elements ) {
- return Sizzle( expr, null, null, elements );
- };
- Sizzle.matchesSelector = function( elem, expr ) {
- // Set document vars if needed
- if ( ( elem.ownerDocument || elem ) !== document ) {
- setDocument( elem );
- }
- // Make sure that attribute selectors are quoted
- expr = expr.replace( rattributeQuotes, "='$1']" );
- if ( support.matchesSelector && documentIsHTML &&
- ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
- ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
- try {
- var ret = matches.call( elem, expr );
- // IE 9's matchesSelector returns false on disconnected nodes
- if ( ret || support.disconnectedMatch ||
- // As well, disconnected nodes are said to be in a document
- // fragment in IE 9
- elem.document && elem.document.nodeType !== 11 ) {
- return ret;
- }
- } catch(e) {}
- }
- return Sizzle( expr, document, null, [ elem ] ).length > 0;
- };
- Sizzle.contains = function( context, elem ) {
- // Set document vars if needed
- if ( ( context.ownerDocument || context ) !== document ) {
- setDocument( context );
- }
- return contains( context, elem );
- };
- Sizzle.attr = function( elem, name ) {
- // Set document vars if needed
- if ( ( elem.ownerDocument || elem ) !== document ) {
- setDocument( elem );
- }
- var fn = Expr.attrHandle[ name.toLowerCase() ],
- // Don't get fooled by Object.prototype properties (jQuery #13807)
- val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
- fn( elem, name, !documentIsHTML ) :
- undefined;
- return val !== undefined ?
- val :
- support.attributes || !documentIsHTML ?
- elem.getAttribute( name ) :
- (val = elem.getAttributeNode(name)) && val.specified ?
- val.value :
- null;
- };
- Sizzle.error = function( msg ) {
- throw new Error( "Syntax error, unrecognized expression: " + msg );
- };
- /**
- * Document sorting and removing duplicates
- * @param {ArrayLike} results
- */
- Sizzle.uniqueSort = function( results ) {
- var elem,
- duplicates = [],
- j = 0,
- i = 0;
- // Unless we *know* we can detect duplicates, assume their presence
- hasDuplicate = !support.detectDuplicates;
- sortInput = !support.sortStable && results.slice( 0 );
- results.sort( sortOrder );
- if ( hasDuplicate ) {
- while ( (elem = results[i++]) ) {
- if ( elem === results[ i ] ) {
- j = duplicates.push( i );
- }
- }
- while ( j-- ) {
- results.splice( duplicates[ j ], 1 );
- }
- }
- // Clear input after sorting to release objects
- // See https://github.com/jquery/sizzle/pull/225
- sortInput = null;
- return results;
- };
- /**
- * Utility function for retrieving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
- getText = Sizzle.getText = function( elem ) {
- var node,
- ret = "",
- i = 0,
- nodeType = elem.nodeType;
- if ( !nodeType ) {
- // If no nodeType, this is expected to be an array
- while ( (node = elem[i++]) ) {
- // Do not traverse comment nodes
- ret += getText( node );
- }
- } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
- // Use textContent for elements
- // innerText usage removed for consistency of new lines (jQuery #11153)
- if ( typeof elem.textContent === "string" ) {
- return elem.textContent;
- } else {
- // Traverse its children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- ret += getText( elem );
- }
- }
- } else if ( nodeType === 3 || nodeType === 4 ) {
- return elem.nodeValue;
- }
- // Do not include comment or processing instruction nodes
- return ret;
- };
- Expr = Sizzle.selectors = {
- // Can be adjusted by the user
- cacheLength: 50,
- createPseudo: markFunction,
- match: matchExpr,
- attrHandle: {},
- find: {},
- relative: {
- ">": { dir: "parentNode", first: true },
- " ": { dir: "parentNode" },
- "+": { dir: "previousSibling", first: true },
- "~": { dir: "previousSibling" }
- },
- preFilter: {
- "ATTR": function( match ) {
- match[1] = match[1].replace( runescape, funescape );
- // Move the given value to match[3] whether quoted or unquoted
- match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
- if ( match[2] === "~=" ) {
- match[3] = " " + match[3] + " ";
- }
- return match.slice( 0, 4 );
- },
- "CHILD": function( match ) {
- /* matches from matchExpr["CHILD"]
- 1 type (only|nth|...)
- 2 what (child|of-type)
- 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
- 4 xn-component of xn+y argument ([+-]?\d*n|)
- 5 sign of xn-component
- 6 x of xn-component
- 7 sign of y-component
- 8 y of y-component
- */
- match[1] = match[1].toLowerCase();
- if ( match[1].slice( 0, 3 ) === "nth" ) {
- // nth-* requires argument
- if ( !match[3] ) {
- Sizzle.error( match[0] );
- }
- // numeric x and y parameters for Expr.filter.CHILD
- // remember that false/true cast respectively to 0/1
- match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
- match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
- // other types prohibit arguments
- } else if ( match[3] ) {
- Sizzle.error( match[0] );
- }
- return match;
- },
- "PSEUDO": function( match ) {
- var excess,
- unquoted = !match[6] && match[2];
- if ( matchExpr["CHILD"].test( match[0] ) ) {
- return null;
- }
- // Accept quoted arguments as-is
- if ( match[3] ) {
- match[2] = match[4] || match[5] || "";
- // Strip excess characters from unquoted arguments
- } else if ( unquoted && rpseudo.test( unquoted ) &&
- // Get excess from tokenize (recursively)
- (excess = tokenize( unquoted, true )) &&
- // advance to the next closing parenthesis
- (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
- // excess is a negative index
- match[0] = match[0].slice( 0, excess );
- match[2] = unquoted.slice( 0, excess );
- }
- // Return only captures needed by the pseudo filter method (type and argument)
- return match.slice( 0, 3 );
- }
- },
- filter: {
- "TAG": function( nodeNameSelector ) {
- var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
- return nodeNameSelector === "*" ?
- function() { return true; } :
- function( elem ) {
- return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
- };
- },
- "CLASS": function( className ) {
- var pattern = classCache[ className + " " ];
- return pattern ||
- (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
- classCache( className, function( elem ) {
- return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
- });
- },
- "ATTR": function( name, operator, check ) {
- return function( elem ) {
- var result = Sizzle.attr( elem, name );
- if ( result == null ) {
- return operator === "!=";
- }
- if ( !operator ) {
- return true;
- }
- result += "";
- return operator === "=" ? result === check :
- operator === "!=" ? result !== check :
- operator === "^=" ? check && result.indexOf( check ) === 0 :
- operator === "*=" ? check && result.indexOf( check ) > -1 :
- operator === "$=" ? check && result.slice( -check.length ) === check :
- operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
- operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
- false;
- };
- },
- "CHILD": function( type, what, argument, first, last ) {
- var simple = type.slice( 0, 3 ) !== "nth",
- forward = type.slice( -4 ) !== "last",
- ofType = what === "of-type";
- return first === 1 && last === 0 ?
- // Shortcut for :nth-*(n)
- function( elem ) {
- return !!elem.parentNode;
- } :
- function( elem, context, xml ) {
- var cache, outerCache, node, diff, nodeIndex, start,
- dir = simple !== forward ? "nextSibling" : "previousSibling",
- parent = elem.parentNode,
- name = ofType && elem.nodeName.toLowerCase(),
- useCache = !xml && !ofType;
- if ( parent ) {
- // :(first|last|only)-(child|of-type)
- if ( simple ) {
- while ( dir ) {
- node = elem;
- while ( (node = node[ dir ]) ) {
- if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
- return false;
- }
- }
- // Reverse direction for :only-* (if we haven't yet done so)
- start = dir = type === "only" && !start && "nextSibling";
- }
- return true;
- }
- start = [ forward ? parent.firstChild : parent.lastChild ];
- // non-xml :nth-child(...) stores cache data on `parent`
- if ( forward && useCache ) {
- // Seek `elem` from a previously-cached index
- outerCache = parent[ expando ] || (parent[ expando ] = {});
- cache = outerCache[ type ] || [];
- nodeIndex = cache[0] === dirruns && cache[1];
- diff = cache[0] === dirruns && cache[2];
- node = nodeIndex && parent.childNodes[ nodeIndex ];
- while ( (node = ++nodeIndex && node && node[ dir ] ||
- // Fallback to seeking `elem` from the start
- (diff = nodeIndex = 0) || start.pop()) ) {
- // When found, cache indexes on `parent` and break
- if ( node.nodeType === 1 && ++diff && node === elem ) {
- outerCache[ type ] = [ dirruns, nodeIndex, diff ];
- break;
- }
- }
- // Use previously-cached element index if available
- } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
- diff = cache[1];
- // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
- } else {
- // Use the same loop as above to seek `elem` from the start
- while ( (node = ++nodeIndex && node && node[ dir ] ||
- (diff = nodeIndex = 0) || start.pop()) ) {
- if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
- // Cache the index of each encountered element
- if ( useCache ) {
- (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
- }
- if ( node === elem ) {
- break;
- }
- }
- }
- }
- // Incorporate the offset, then check against cycle size
- diff -= last;
- return diff === first || ( diff % first === 0 && diff / first >= 0 );
- }
- };
- },
- "PSEUDO": function( pseudo, argument ) {
- // pseudo-class names are case-insensitive
- // http://www.w3.org/TR/selectors/#pseudo-classes
- // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
- // Remember that setFilters inherits from pseudos
- var args,
- fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
- Sizzle.error( "unsupported pseudo: " + pseudo );
- // The user may use createPseudo to indicate that
- // arguments are needed to create the filter function
- // just as Sizzle does
- if ( fn[ expando ] ) {
- return fn( argument );
- }
- // But maintain support for old signatures
- if ( fn.length > 1 ) {
- args = [ pseudo, pseudo, "", argument ];
- return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
- markFunction(function( seed, matches ) {
- var idx,
- matched = fn( seed, argument ),
- i = matched.length;
- while ( i-- ) {
- idx = indexOf.call( seed, matched[i] );
- seed[ idx ] = !( matches[ idx ] = matched[i] );
- }
- }) :
- function( elem ) {
- return fn( elem, 0, args );
- };
- }
- return fn;
- }
- },
- pseudos: {
- // Potentially complex pseudos
- "not": markFunction(function( selector ) {
- // Trim the selector passed to compile
- // to avoid treating leading and trailing
- // spaces as combinators
- var input = [],
- results = [],
- matcher = compile( selector.replace( rtrim, "$1" ) );
- return matcher[ expando ] ?
- markFunction(function( seed, matches, context, xml ) {
- var elem,
- unmatched = matcher( seed, null, xml, [] ),
- i = seed.length;
- // Match elements unmatched by `matcher`
- while ( i-- ) {
- if ( (elem = unmatched[i]) ) {
- seed[i] = !(matches[i] = elem);
- }
- }
- }) :
- function( elem, context, xml ) {
- input[0] = elem;
- matcher( input, null, xml, results );
- return !results.pop();
- };
- }),
- "has": markFunction(function( selector ) {
- return function( elem ) {
- return Sizzle( selector, elem ).length > 0;
- };
- }),
- "contains": markFunction(function( text ) {
- text = text.replace( runescape, funescape );
- return function( elem ) {
- return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
- };
- }),
- // "Whether an element is represented by a :lang() selector
- // is based solely on the element's language value
- // being equal to the identifier C,
- // or beginning with the identifier C immediately followed by "-".
- // The matching of C against the element's language value is performed case-insensitively.
- // The identifier C does not have to be a valid language name."
- // http://www.w3.org/TR/selectors/#lang-pseudo
- "lang": markFunction( function( lang ) {
- // lang value must be a valid identifier
- if ( !ridentifier.test(lang || "") ) {
- Sizzle.error( "unsupported lang: " + lang );
- }
- lang = lang.replace( runescape, funescape ).toLowerCase();
- return function( elem ) {
- var elemLang;
- do {
- if ( (elemLang = documentIsHTML ?
- elem.lang :
- elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
- elemLang = elemLang.toLowerCase();
- return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
- }
- } while ( (elem = elem.parentNode) && elem.nodeType === 1 );
- return false;
- };
- }),
- // Miscellaneous
- "target": function( elem ) {
- var hash = window.location && window.location.hash;
- return hash && hash.slice( 1 ) === elem.id;
- },
- "root": function( elem ) {
- return elem === docElem;
- },
- "focus": function( elem ) {
- return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
- },
- // Boolean properties
- "enabled": function( elem ) {
- return elem.disabled === false;
- },
- "disabled": function( elem ) {
- return elem.disabled === true;
- },
- "checked": function( elem ) {
- // In CSS3, :checked should return both checked and selected elements
- // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
- var nodeName = elem.nodeName.toLowerCase();
- return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
- },
- "selected": function( elem ) {
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- if ( elem.parentNode ) {
- elem.parentNode.selectedIndex;
- }
- return elem.selected === true;
- },
- // Contents
- "empty": function( elem ) {
- // http://www.w3.org/TR/selectors/#empty-pseudo
- // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
- // but not by others (comment: 8; processing instruction: 7; etc.)
- // nodeType < 6 works because attributes (2) do not appear as children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
- if ( elem.nodeType < 6 ) {
- return false;
- }
- }
- return true;
- },
- "parent": function( elem ) {
- return !Expr.pseudos["empty"]( elem );
- },
- // Element/input types
- "header": function( elem ) {
- return rheader.test( elem.nodeName );
- },
- "input": function( elem ) {
- return rinputs.test( elem.nodeName );
- },
- "button": function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && elem.type === "button" || name === "button";
- },
- "text": function( elem ) {
- var attr;
- return elem.nodeName.toLowerCase() === "input" &&
- elem.type === "text" &&
- // Support: IE<8
- // New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
- ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
- },
- // Position-in-collection
- "first": createPositionalPseudo(function() {
- return [ 0 ];
- }),
- "last": createPositionalPseudo(function( matchIndexes, length ) {
- return [ length - 1 ];
- }),
- "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
- return [ argument < 0 ? argument + length : argument ];
- }),
- "even": createPositionalPseudo(function( matchIndexes, length ) {
- var i = 0;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
- "odd": createPositionalPseudo(function( matchIndexes, length ) {
- var i = 1;
- for ( ; i < length; i += 2 ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
- "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; --i >= 0; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- }),
- "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
- var i = argument < 0 ? argument + length : argument;
- for ( ; ++i < length; ) {
- matchIndexes.push( i );
- }
- return matchIndexes;
- })
- }
- };
- Expr.pseudos["nth"] = Expr.pseudos["eq"];
- // Add button/input type pseudos
- for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
- Expr.pseudos[ i ] = createInputPseudo( i );
- }
- for ( i in { submit: true, reset: true } ) {
- Expr.pseudos[ i ] = createButtonPseudo( i );
- }
- // Easy API for creating new setFilters
- function setFilters() {}
- setFilters.prototype = Expr.filters = Expr.pseudos;
- Expr.setFilters = new setFilters();
- tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
- var matched, match, tokens, type,
- soFar, groups, preFilters,
- cached = tokenCache[ selector + " " ];
- if ( cached ) {
- return parseOnly ? 0 : cached.slice( 0 );
- }
- soFar = selector;
- groups = [];
- preFilters = Expr.preFilter;
- while ( soFar ) {
- // Comma and first run
- if ( !matched || (match = rcomma.exec( soFar )) ) {
- if ( match ) {
- // Don't consume trailing commas as valid
- soFar = soFar.slice( match[0].length ) || soFar;
- }
- groups.push( (tokens = []) );
- }
- matched = false;
- // Combinators
- if ( (match = rcombinators.exec( soFar )) ) {
- matched = match.shift();
- tokens.push({
- value: matched,
- // Cast descendant combinators to space
- type: match[0].replace( rtrim, " " )
- });
- soFar = soFar.slice( matched.length );
- }
- // Filters
- for ( type in Expr.filter ) {
- if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
- (match = preFilters[ type ]( match ))) ) {
- matched = match.shift();
- tokens.push({
- value: matched,
- type: type,
- matches: match
- });
- soFar = soFar.slice( matched.length );
- }
- }
- if ( !matched ) {
- break;
- }
- }
- // Return the length of the invalid excess
- // if we're just parsing
- // Otherwise, throw an error or return tokens
- return parseOnly ?
- soFar.length :
- soFar ?
- Sizzle.error( selector ) :
- // Cache the tokens
- tokenCache( selector, groups ).slice( 0 );
- };
- function toSelector( tokens ) {
- var i = 0,
- len = tokens.length,
- selector = "";
- for ( ; i < len; i++ ) {
- selector += tokens[i].value;
- }
- return selector;
- }
- function addCombinator( matcher, combinator, base ) {
- var dir = combinator.dir,
- checkNonElements = base && dir === "parentNode",
- doneName = done++;
- return combinator.first ?
- // Check against closest ancestor/preceding element
- function( elem, context, xml ) {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- return matcher( elem, context, xml );
- }
- }
- } :
- // Check against all ancestor/preceding elements
- function( elem, context, xml ) {
- var oldCache, outerCache,
- newCache = [ dirruns, doneName ];
- // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
- if ( xml ) {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- if ( matcher( elem, context, xml ) ) {
- return true;
- }
- }
- }
- } else {
- while ( (elem = elem[ dir ]) ) {
- if ( elem.nodeType === 1 || checkNonElements ) {
- outerCache = elem[ expando ] || (elem[ expando ] = {});
- if ( (oldCache = outerCache[ dir ]) &&
- oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
- // Assign to newCache so results back-propagate to previous elements
- return (newCache[ 2 ] = oldCache[ 2 ]);
- } else {
- // Reuse newcache so results back-propagate to previous elements
- outerCache[ dir ] = newCache;
- // A match means we're done; a fail means we have to keep checking
- if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
- return true;
- }
- }
- }
- }
- }
- };
- }
- function elementMatcher( matchers ) {
- return matchers.length > 1 ?
- function( elem, context, xml ) {
- var i = matchers.length;
- while ( i-- ) {
- if ( !matchers[i]( elem, context, xml ) ) {
- return false;
- }
- }
- return true;
- } :
- matchers[0];
- }
- function multipleContexts( selector, contexts, results ) {
- var i = 0,
- len = contexts.length;
- for ( ; i < len; i++ ) {
- Sizzle( selector, contexts[i], results );
- }
- return results;
- }
- function condense( unmatched, map, filter, context, xml ) {
- var elem,
- newUnmatched = [],
- i = 0,
- len = unmatched.length,
- mapped = map != null;
- for ( ; i < len; i++ ) {
- if ( (elem = unmatched[i]) ) {
- if ( !filter || filter( elem, context, xml ) ) {
- newUnmatched.push( elem );
- if ( mapped ) {
- map.push( i );
- }
- }
- }
- }
- return newUnmatched;
- }
- function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
- if ( postFilter && !postFilter[ expando ] ) {
- postFilter = setMatcher( postFilter );
- }
- if ( postFinder && !postFinder[ expando ] ) {
- postFinder = setMatcher( postFinder, postSelector );
- }
- return markFunction(function( seed, results, context, xml ) {
- var temp, i, elem,
- preMap = [],
- postMap = [],
- preexisting = results.length,
- // Get initial elements from seed or context
- elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
- // Prefilter to get matcher input, preserving a map for seed-results synchronization
- matcherIn = preFilter && ( seed || !selector ) ?
- condense( elems, preMap, preFilter, context, xml ) :
- elems,
- matcherOut = matcher ?
- // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
- postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
- // ...intermediate processing is necessary
- [] :
- // ...otherwise use results directly
- results :
- matcherIn;
- // Find primary matches
- if ( matcher ) {
- matcher( matcherIn, matcherOut, context, xml );
- }
- // Apply postFilter
- if ( postFilter ) {
- temp = condense( matcherOut, postMap );
- postFilter( temp, [], context, xml );
- // Un-match failing elements by moving them back to matcherIn
- i = temp.length;
- while ( i-- ) {
- if ( (elem = temp[i]) ) {
- matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
- }
- }
- }
- if ( seed ) {
- if ( postFinder || preFilter ) {
- if ( postFinder ) {
- // Get the final matcherOut by condensing this intermediate into postFinder contexts
- temp = [];
- i = matcherOut.length;
- while ( i-- ) {
- if ( (elem = matcherOut[i]) ) {
- // Restore matcherIn since elem is not yet a final match
- temp.push( (matcherIn[i] = elem) );
- }
- }
- postFinder( null, (matcherOut = []), temp, xml );
- }
- // Move matched elements from seed to results to keep them synchronized
- i = matcherOut.length;
- while ( i-- ) {
- if ( (elem = matcherOut[i]) &&
- (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
- seed[temp] = !(results[temp] = elem);
- }
- }
- }
- // Add elements to results, through postFinder if defined
- } else {
- matcherOut = condense(
- matcherOut === results ?
- matcherOut.splice( preexisting, matcherOut.length ) :
- matcherOut
- );
- if ( postFinder ) {
- postFinder( null, results, matcherOut, xml );
- } else {
- push.apply( results, matcherOut );
- }
- }
- });
- }
- function matcherFromTokens( tokens ) {
- var checkContext, matcher, j,
- len = tokens.length,
- leadingRelative = Expr.relative[ tokens[0].type ],
- implicitRelative = leadingRelative || Expr.relative[" "],
- i = leadingRelative ? 1 : 0,
- // The foundational matcher ensures that elements are reachable from top-level context(s)
- matchContext = addCombinator( function( elem ) {
- return elem === checkContext;
- }, implicitRelative, true ),
- matchAnyContext = addCombinator( function( elem ) {
- return indexOf.call( checkContext, elem ) > -1;
- }, implicitRelative, true ),
- matchers = [ function( elem, context, xml ) {
- return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
- (checkContext = context).nodeType ?
- matchContext( elem, context, xml ) :
- matchAnyContext( elem, context, xml ) );
- } ];
- for ( ; i < len; i++ ) {
- if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
- matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
- } else {
- matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
- // Return special upon seeing a positional matcher
- if ( matcher[ expando ] ) {
- // Find the next relative operator (if any) for proper handling
- j = ++i;
- for ( ; j < len; j++ ) {
- if ( Expr.relative[ tokens[j].type ] ) {
- break;
- }
- }
- return setMatcher(
- i > 1 && elementMatcher( matchers ),
- i > 1 && toSelector(
- // If the preceding token was a descendant combinator, insert an implicit any-element `*`
- tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
- ).replace( rtrim, "$1" ),
- matcher,
- i < j && matcherFromTokens( tokens.slice( i, j ) ),
- j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
- j < len && toSelector( tokens )
- );
- }
- matchers.push( matcher );
- }
- }
- return elementMatcher( matchers );
- }
- function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
- var bySet = setMatchers.length > 0,
- byElement = elementMatchers.length > 0,
- superMatcher = function( seed, context, xml, results, outermost ) {
- var elem, j, matcher,
- matchedCount = 0,
- i = "0",
- unmatched = seed && [],
- setMatched = [],
- contextBackup = outermostContext,
- // We must always have either seed elements or outermost context
- elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
- // Use integer dirruns iff this is the outermost matcher
- dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
- len = elems.length;
- if ( outermost ) {
- outermostContext = context !== document && context;
- }
- // Add elements passing elementMatchers directly to results
- // Keep `i` a string if there are no elements so `matchedCount` will be "00" below
- // Support: IE<9, Safari
- // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
- for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
- if ( byElement && elem ) {
- j = 0;
- while ( (matcher = elementMatchers[j++]) ) {
- if ( matcher( elem, context, xml ) ) {
- results.push( elem );
- break;
- }
- }
- if ( outermost ) {
- dirruns = dirrunsUnique;
- }
- }
- // Track unmatched elements for set filters
- if ( bySet ) {
- // They will have gone through all possible matchers
- if ( (elem = !matcher && elem) ) {
- matchedCount--;
- }
- // Lengthen the array for every element, matched or not
- if ( seed ) {
- unmatched.push( elem );
- }
- }
- }
- // Apply set filters to unmatched elements
- matchedCount += i;
- if ( bySet && i !== matchedCount ) {
- j = 0;
- while ( (matcher = setMatchers[j++]) ) {
- matcher( unmatched, setMatched, context, xml );
- }
- if ( seed ) {
- // Reintegrate element matches to eliminate the need for sorting
- if ( matchedCount > 0 ) {
- while ( i-- ) {
- if ( !(unmatched[i] || setMatched[i]) ) {
- setMatched[i] = pop.call( results );
- }
- }
- }
- // Discard index placeholder values to get only actual matches
- setMatched = condense( setMatched );
- }
- // Add matches to results
- push.apply( results, setMatched );
- // Seedless set matches succeeding multiple successful matchers stipulate sorting
- if ( outermost && !seed && setMatched.length > 0 &&
- ( matchedCount + setMatchers.length ) > 1 ) {
- Sizzle.uniqueSort( results );
- }
- }
- // Override manipulation of globals by nested matchers
- if ( outermost ) {
- dirruns = dirrunsUnique;
- outermostContext = contextBackup;
- }
- return unmatched;
- };
- return bySet ?
- markFunction( superMatcher ) :
- superMatcher;
- }
- compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
- var i,
- setMatchers = [],
- elementMatchers = [],
- cached = compilerCache[ selector + " " ];
- if ( !cached ) {
- // Generate a function of recursive functions that can be used to check each element
- if ( !match ) {
- match = tokenize( selector );
- }
- i = match.length;
- while ( i-- ) {
- cached = matcherFromTokens( match[i] );
- if ( cached[ expando ] ) {
- setMatchers.push( cached );
- } else {
- elementMatchers.push( cached );
- }
- }
- // Cache the compiled function
- cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
- // Save selector and tokenization
- cached.selector = selector;
- }
- return cached;
- };
- /**
- * A low-level selection function that works with Sizzle's compiled
- * selector functions
- * @param {String|Function} selector A selector or a pre-compiled
- * selector function built with Sizzle.compile
- * @param {Element} context
- * @param {Array} [results]
- * @param {Array} [seed] A set of elements to match against
- */
- select = Sizzle.select = function( selector, context, results, seed ) {
- var i, tokens, token, type, find,
- compiled = typeof selector === "function" && selector,
- match = !seed && tokenize( (selector = compiled.selector || selector) );
- results = results || [];
- // Try to minimize operations if there is no seed and only one group
- if ( match.length === 1 ) {
- // Take a shortcut and set the context if the root selector is an ID
- tokens = match[0] = match[0].slice( 0 );
- if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
- support.getById && context.nodeType === 9 && documentIsHTML &&
- Expr.relative[ tokens[1].type ] ) {
- context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
- if ( !context ) {
- return results;
- // Precompiled matchers will still verify ancestry, so step up a level
- } else if ( compiled ) {
- context = context.parentNode;
- }
- selector = selector.slice( tokens.shift().value.length );
- }
- // Fetch a seed set for right-to-left matching
- i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
- while ( i-- ) {
- token = tokens[i];
- // Abort if we hit a combinator
- if ( Expr.relative[ (type = token.type) ] ) {
- break;
- }
- if ( (find = Expr.find[ type ]) ) {
- // Search, expanding context for leading sibling combinators
- if ( (seed = find(
- token.matches[0].replace( runescape, funescape ),
- rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
- )) ) {
- // If seed is empty or no tokens remain, we can return early
- tokens.splice( i, 1 );
- selector = seed.length && toSelector( tokens );
- if ( !selector ) {
- push.apply( results, seed );
- return results;
- }
- break;
- }
- }
- }
- }
- // Compile and execute a filtering function if one is not provided
- // Provide `match` to avoid retokenization if we modified the selector above
- ( compiled || compile( selector, match ) )(
- seed,
- context,
- !documentIsHTML,
- results,
- rsibling.test( selector ) && testContext( context.parentNode ) || context
- );
- return results;
- };
- // One-time assignments
- // Sort stability
- support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
- // Support: Chrome 14-35+
- // Always assume duplicates if they aren't passed to the comparison function
- support.detectDuplicates = !!hasDuplicate;
- // Initialize against the default document
- setDocument();
- // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
- // Detached nodes confoundingly follow *each other*
- support.sortDetached = assert(function( div1 ) {
- // Should return 1, but returns 4 (following)
- return div1.compareDocumentPosition( document.createElement("div") ) & 1;
- });
- // Support: IE<8
- // Prevent attribute/property "interpolation"
- // http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
- if ( !assert(function( div ) {
- div.innerHTML = "<a href='#'></a>";
- return div.firstChild.getAttribute("href") === "#" ;
- }) ) {
- addHandle( "type|href|height|width", function( elem, name, isXML ) {
- if ( !isXML ) {
- return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
- }
- });
- }
- // Support: IE<9
- // Use defaultValue in place of getAttribute("value")
- if ( !support.attributes || !assert(function( div ) {
- div.innerHTML = "<input/>";
- div.firstChild.setAttribute( "value", "" );
- return div.firstChild.getAttribute( "value" ) === "";
- }) ) {
- addHandle( "value", function( elem, name, isXML ) {
- if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
- return elem.defaultValue;
- }
- });
- }
- // Support: IE<9
- // Use getAttributeNode to fetch booleans when getAttribute lies
- if ( !assert(function( div ) {
- return div.getAttribute("disabled") == null;
- }) ) {
- addHandle( booleans, function( elem, name, isXML ) {
- var val;
- if ( !isXML ) {
- return elem[ name ] === true ? name.toLowerCase() :
- (val = elem.getAttributeNode( name )) && val.specified ?
- val.value :
- null;
- }
- });
- }
- // EXPOSE
- return Sizzle;
- });
- /*eslint-enable */
- // Included from: js/tinymce/classes/util/Arr.js
- /**
- * Arr.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Array utility class.
- *
- * @private
- * @class tinymce.util.Arr
- */
- define("tinymce/util/Arr", [], function() {
- var isArray = Array.isArray || function(obj) {
- return Object.prototype.toString.call(obj) === "[object Array]";
- };
- function toArray(obj) {
- var array = obj, i, l;
- if (!isArray(obj)) {
- array = [];
- for (i = 0, l = obj.length; i < l; i++) {
- array[i] = obj[i];
- }
- }
- return array;
- }
- function each(o, cb, s) {
- var n, l;
- if (!o) {
- return 0;
- }
- s = s || o;
- if (o.length !== undefined) {
- // Indexed arrays, needed for Safari
- for (n = 0, l = o.length; n < l; n++) {
- if (cb.call(s, o[n], n, o) === false) {
- return 0;
- }
- }
- } else {
- // Hashtables
- for (n in o) {
- if (o.hasOwnProperty(n)) {
- if (cb.call(s, o[n], n, o) === false) {
- return 0;
- }
- }
- }
- }
- return 1;
- }
- function map(array, callback) {
- var out = [];
- each(array, function(item, index) {
- out.push(callback(item, index, array));
- });
- return out;
- }
- function filter(a, f) {
- var o = [];
- each(a, function(v, index) {
- if (!f || f(v, index, a)) {
- o.push(v);
- }
- });
- return o;
- }
- function indexOf(a, v) {
- var i, l;
- if (a) {
- for (i = 0, l = a.length; i < l; i++) {
- if (a[i] === v) {
- return i;
- }
- }
- }
- return -1;
- }
- function reduce(collection, iteratee, accumulator, thisArg) {
- var i = 0;
- if (arguments.length < 3) {
- accumulator = collection[0];
- }
- for (; i < collection.length; i++) {
- accumulator = iteratee.call(thisArg, accumulator, collection[i], i);
- }
- return accumulator;
- }
- function findIndex(array, predicate, thisArg) {
- var i, l;
- for (i = 0, l = array.length; i < l; i++) {
- if (predicate.call(thisArg, array[i], i, array)) {
- return i;
- }
- }
- return -1;
- }
- function find(array, predicate, thisArg) {
- var idx = findIndex(array, predicate, thisArg);
- if (idx !== -1) {
- return array[idx];
- }
- return undefined;
- }
- function last(collection) {
- return collection[collection.length - 1];
- }
- return {
- isArray: isArray,
- toArray: toArray,
- each: each,
- map: map,
- filter: filter,
- indexOf: indexOf,
- reduce: reduce,
- findIndex: findIndex,
- find: find,
- last: last
- };
- });
- // Included from: js/tinymce/classes/util/Tools.js
- /**
- * Tools.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class contains various utlity functions. These are also exposed
- * directly on the tinymce namespace.
- *
- * @class tinymce.util.Tools
- */
- define("tinymce/util/Tools", [
- "tinymce/Env",
- "tinymce/util/Arr"
- ], function(Env, Arr) {
- /**
- * Removes whitespace from the beginning and end of a string.
- *
- * @method trim
- * @param {String} s String to remove whitespace from.
- * @return {String} New string with removed whitespace.
- */
- var whiteSpaceRegExp = /^\s*|\s*$/g;
- function trim(str) {
- return (str === null || str === undefined) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
- }
- /**
- * Checks if a object is of a specific type for example an array.
- *
- * @method is
- * @param {Object} obj Object to check type of.
- * @param {string} type Optional type to check for.
- * @return {Boolean} true/false if the object is of the specified type.
- */
- function is(obj, type) {
- if (!type) {
- return obj !== undefined;
- }
- if (type == 'array' && Arr.isArray(obj)) {
- return true;
- }
- return typeof obj == type;
- }
- /**
- * Makes a name/object map out of an array with names.
- *
- * @method makeMap
- * @param {Array/String} items Items to make map out of.
- * @param {String} delim Optional delimiter to split string by.
- * @param {Object} map Optional map to add items to.
- * @return {Object} Name/value map of items.
- */
- function makeMap(items, delim, map) {
- var i;
- items = items || [];
- delim = delim || ',';
- if (typeof items == "string") {
- items = items.split(delim);
- }
- map = map || {};
- i = items.length;
- while (i--) {
- map[items[i]] = {};
- }
- return map;
- }
- /**
- * Creates a class, subclass or static singleton.
- * More details on this method can be found in the Wiki.
- *
- * @method create
- * @param {String} s Class name, inheritance and prefix.
- * @param {Object} p Collection of methods to add to the class.
- * @param {Object} root Optional root object defaults to the global window object.
- * @example
- * // Creates a basic class
- * tinymce.create('tinymce.somepackage.SomeClass', {
- * SomeClass: function() {
- * // Class constructor
- * },
- *
- * method: function() {
- * // Some method
- * }
- * });
- *
- * // Creates a basic subclass class
- * tinymce.create('tinymce.somepackage.SomeSubClass:tinymce.somepackage.SomeClass', {
- * SomeSubClass: function() {
- * // Class constructor
- * this.parent(); // Call parent constructor
- * },
- *
- * method: function() {
- * // Some method
- * this.parent(); // Call parent method
- * },
- *
- * 'static': {
- * staticMethod: function() {
- * // Static method
- * }
- * }
- * });
- *
- * // Creates a singleton/static class
- * tinymce.create('static tinymce.somepackage.SomeSingletonClass', {
- * method: function() {
- * // Some method
- * }
- * });
- */
- function create(s, p, root) {
- var self = this, sp, ns, cn, scn, c, de = 0;
- // Parse : <prefix> <class>:<super class>
- s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
- cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
- // Create namespace for new class
- ns = self.createNS(s[3].replace(/\.\w+$/, ''), root);
- // Class already exists
- if (ns[cn]) {
- return;
- }
- // Make pure static class
- if (s[2] == 'static') {
- ns[cn] = p;
- if (this.onCreate) {
- this.onCreate(s[2], s[3], ns[cn]);
- }
- return;
- }
- // Create default constructor
- if (!p[cn]) {
- p[cn] = function() {};
- de = 1;
- }
- // Add constructor and methods
- ns[cn] = p[cn];
- self.extend(ns[cn].prototype, p);
- // Extend
- if (s[5]) {
- sp = self.resolve(s[5]).prototype;
- scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
- // Extend constructor
- c = ns[cn];
- if (de) {
- // Add passthrough constructor
- ns[cn] = function() {
- return sp[scn].apply(this, arguments);
- };
- } else {
- // Add inherit constructor
- ns[cn] = function() {
- this.parent = sp[scn];
- return c.apply(this, arguments);
- };
- }
- ns[cn].prototype[cn] = ns[cn];
- // Add super methods
- self.each(sp, function(f, n) {
- ns[cn].prototype[n] = sp[n];
- });
- // Add overridden methods
- self.each(p, function(f, n) {
- // Extend methods if needed
- if (sp[n]) {
- ns[cn].prototype[n] = function() {
- this.parent = sp[n];
- return f.apply(this, arguments);
- };
- } else {
- if (n != cn) {
- ns[cn].prototype[n] = f;
- }
- }
- });
- }
- // Add static methods
- /*jshint sub:true*/
- /*eslint dot-notation:0*/
- self.each(p['static'], function(f, n) {
- ns[cn][n] = f;
- });
- }
- function extend(obj, ext) {
- var i, l, name, args = arguments, value;
- for (i = 1, l = args.length; i < l; i++) {
- ext = args[i];
- for (name in ext) {
- if (ext.hasOwnProperty(name)) {
- value = ext[name];
- if (value !== undefined) {
- obj[name] = value;
- }
- }
- }
- }
- return obj;
- }
- /**
- * Executed the specified function for each item in a object tree.
- *
- * @method walk
- * @param {Object} o Object tree to walk though.
- * @param {function} f Function to call for each item.
- * @param {String} n Optional name of collection inside the objects to walk for example childNodes.
- * @param {String} s Optional scope to execute the function in.
- */
- function walk(o, f, n, s) {
- s = s || this;
- if (o) {
- if (n) {
- o = o[n];
- }
- Arr.each(o, function(o, i) {
- if (f.call(s, o, i, n) === false) {
- return false;
- }
- walk(o, f, n, s);
- });
- }
- }
- /**
- * Creates a namespace on a specific object.
- *
- * @method createNS
- * @param {String} n Namespace to create for example a.b.c.d.
- * @param {Object} o Optional object to add namespace to, defaults to window.
- * @return {Object} New namespace object the last item in path.
- * @example
- * // Create some namespace
- * tinymce.createNS('tinymce.somepackage.subpackage');
- *
- * // Add a singleton
- * var tinymce.somepackage.subpackage.SomeSingleton = {
- * method: function() {
- * // Some method
- * }
- * };
- */
- function createNS(n, o) {
- var i, v;
- o = o || window;
- n = n.split('.');
- for (i = 0; i < n.length; i++) {
- v = n[i];
- if (!o[v]) {
- o[v] = {};
- }
- o = o[v];
- }
- return o;
- }
- /**
- * Resolves a string and returns the object from a specific structure.
- *
- * @method resolve
- * @param {String} n Path to resolve for example a.b.c.d.
- * @param {Object} o Optional object to search though, defaults to window.
- * @return {Object} Last object in path or null if it couldn't be resolved.
- * @example
- * // Resolve a path into an object reference
- * var obj = tinymce.resolve('a.b.c.d');
- */
- function resolve(n, o) {
- var i, l;
- o = o || window;
- n = n.split('.');
- for (i = 0, l = n.length; i < l; i++) {
- o = o[n[i]];
- if (!o) {
- break;
- }
- }
- return o;
- }
- /**
- * Splits a string but removes the whitespace before and after each value.
- *
- * @method explode
- * @param {string} s String to split.
- * @param {string} d Delimiter to split by.
- * @example
- * // Split a string into an array with a,b,c
- * var arr = tinymce.explode('a, b, c');
- */
- function explode(s, d) {
- if (!s || is(s, 'array')) {
- return s;
- }
- return Arr.map(s.split(d || ','), trim);
- }
- function _addCacheSuffix(url) {
- var cacheSuffix = Env.cacheSuffix;
- if (cacheSuffix) {
- url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix;
- }
- return url;
- }
- return {
- trim: trim,
- /**
- * Returns true/false if the object is an array or not.
- *
- * @method isArray
- * @param {Object} obj Object to check.
- * @return {boolean} true/false state if the object is an array or not.
- */
- isArray: Arr.isArray,
- is: is,
- /**
- * Converts the specified object into a real JavaScript array.
- *
- * @method toArray
- * @param {Object} obj Object to convert into array.
- * @return {Array} Array object based in input.
- */
- toArray: Arr.toArray,
- makeMap: makeMap,
- /**
- * Performs an iteration of all items in a collection such as an object or array. This method will execure the
- * callback function for each item in the collection, if the callback returns false the iteration will terminate.
- * The callback has the following format: cb(value, key_or_index).
- *
- * @method each
- * @param {Object} o Collection to iterate.
- * @param {function} cb Callback function to execute for each item.
- * @param {Object} s Optional scope to execute the callback in.
- * @example
- * // Iterate an array
- * tinymce.each([1,2,3], function(v, i) {
- * console.debug("Value: " + v + ", Index: " + i);
- * });
- *
- * // Iterate an object
- * tinymce.each({a: 1, b: 2, c: 3], function(v, k) {
- * console.debug("Value: " + v + ", Key: " + k);
- * });
- */
- each: Arr.each,
- /**
- * Creates a new array by the return value of each iteration function call. This enables you to convert
- * one array list into another.
- *
- * @method map
- * @param {Array} array Array of items to iterate.
- * @param {function} callback Function to call for each item. It's return value will be the new value.
- * @return {Array} Array with new values based on function return values.
- */
- map: Arr.map,
- /**
- * Filters out items from the input array by calling the specified function for each item.
- * If the function returns false the item will be excluded if it returns true it will be included.
- *
- * @method grep
- * @param {Array} a Array of items to loop though.
- * @param {function} f Function to call for each item. Include/exclude depends on it's return value.
- * @return {Array} New array with values imported and filtered based in input.
- * @example
- * // Filter out some items, this will return an array with 4 and 5
- * var items = tinymce.grep([1,2,3,4,5], function(v) {return v > 3;});
- */
- grep: Arr.filter,
- /**
- * Returns true/false if the object is an array or not.
- *
- * @method isArray
- * @param {Object} obj Object to check.
- * @return {boolean} true/false state if the object is an array or not.
- */
- inArray: Arr.indexOf,
- extend: extend,
- create: create,
- walk: walk,
- createNS: createNS,
- resolve: resolve,
- explode: explode,
- _addCacheSuffix: _addCacheSuffix
- };
- });
- // Included from: js/tinymce/classes/dom/DomQuery.js
- /**
- * DomQuery.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class mimics most of the jQuery API:
- *
- * This is whats currently implemented:
- * - Utility functions
- * - DOM traversial
- * - DOM manipulation
- * - Event binding
- *
- * This is not currently implemented:
- * - Dimension
- * - Ajax
- * - Animation
- * - Advanced chaining
- *
- * @example
- * var $ = tinymce.dom.DomQuery;
- * $('p').attr('attr', 'value').addClass('class');
- *
- * @class tinymce.dom.DomQuery
- */
- define("tinymce/dom/DomQuery", [
- "tinymce/dom/EventUtils",
- "tinymce/dom/Sizzle",
- "tinymce/util/Tools",
- "tinymce/Env"
- ], function(EventUtils, Sizzle, Tools, Env) {
- var doc = document, push = Array.prototype.push, slice = Array.prototype.slice;
- var rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/;
- var Event = EventUtils.Event, undef;
- var skipUniques = Tools.makeMap('children,contents,next,prev');
- function isDefined(obj) {
- return typeof obj !== 'undefined';
- }
- function isString(obj) {
- return typeof obj === 'string';
- }
- function isWindow(obj) {
- return obj && obj == obj.window;
- }
- function createFragment(html, fragDoc) {
- var frag, node, container;
- fragDoc = fragDoc || doc;
- container = fragDoc.createElement('div');
- frag = fragDoc.createDocumentFragment();
- container.innerHTML = html;
- while ((node = container.firstChild)) {
- frag.appendChild(node);
- }
- return frag;
- }
- function domManipulate(targetNodes, sourceItem, callback, reverse) {
- var i;
- if (isString(sourceItem)) {
- sourceItem = createFragment(sourceItem, getElementDocument(targetNodes[0]));
- } else if (sourceItem.length && !sourceItem.nodeType) {
- sourceItem = DomQuery.makeArray(sourceItem);
- if (reverse) {
- for (i = sourceItem.length - 1; i >= 0; i--) {
- domManipulate(targetNodes, sourceItem[i], callback, reverse);
- }
- } else {
- for (i = 0; i < sourceItem.length; i++) {
- domManipulate(targetNodes, sourceItem[i], callback, reverse);
- }
- }
- return targetNodes;
- }
- if (sourceItem.nodeType) {
- i = targetNodes.length;
- while (i--) {
- callback.call(targetNodes[i], sourceItem);
- }
- }
- return targetNodes;
- }
- function hasClass(node, className) {
- return node && className && (' ' + node.className + ' ').indexOf(' ' + className + ' ') !== -1;
- }
- function wrap(elements, wrapper, all) {
- var lastParent, newWrapper;
- wrapper = DomQuery(wrapper)[0];
- elements.each(function() {
- var self = this;
- if (!all || lastParent != self.parentNode) {
- lastParent = self.parentNode;
- newWrapper = wrapper.cloneNode(false);
- self.parentNode.insertBefore(newWrapper, self);
- newWrapper.appendChild(self);
- } else {
- newWrapper.appendChild(self);
- }
- });
- return elements;
- }
- var numericCssMap = Tools.makeMap('fillOpacity fontWeight lineHeight opacity orphans widows zIndex zoom', ' ');
- var booleanMap = Tools.makeMap('checked compact declare defer disabled ismap multiple nohref noshade nowrap readonly selected', ' ');
- var propFix = {
- 'for': 'htmlFor',
- 'class': 'className',
- 'readonly': 'readOnly'
- };
- var cssFix = {
- 'float': 'cssFloat'
- };
- var attrHooks = {}, cssHooks = {};
- function DomQuery(selector, context) {
- /*eslint new-cap:0 */
- return new DomQuery.fn.init(selector, context);
- }
- function inArray(item, array) {
- var i;
- if (array.indexOf) {
- return array.indexOf(item);
- }
- i = array.length;
- while (i--) {
- if (array[i] === item) {
- return i;
- }
- }
- return -1;
- }
- var whiteSpaceRegExp = /^\s*|\s*$/g;
- function trim(str) {
- return (str === null || str === undef) ? '' : ("" + str).replace(whiteSpaceRegExp, '');
- }
- function each(obj, callback) {
- var length, key, i, undef, value;
- if (obj) {
- length = obj.length;
- if (length === undef) {
- // Loop object items
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- value = obj[key];
- if (callback.call(value, key, value) === false) {
- break;
- }
- }
- }
- } else {
- // Loop array items
- for (i = 0; i < length; i++) {
- value = obj[i];
- if (callback.call(value, i, value) === false) {
- break;
- }
- }
- }
- }
- return obj;
- }
- function grep(array, callback) {
- var out = [];
- each(array, function(i, item) {
- if (callback(item, i)) {
- out.push(item);
- }
- });
- return out;
- }
- function getElementDocument(element) {
- if (!element) {
- return doc;
- }
- if (element.nodeType == 9) {
- return element;
- }
- return element.ownerDocument;
- }
- DomQuery.fn = DomQuery.prototype = {
- constructor: DomQuery,
- /**
- * Selector for the current set.
- *
- * @property selector
- * @type String
- */
- selector: "",
- /**
- * Context used to create the set.
- *
- * @property context
- * @type Element
- */
- context: null,
- /**
- * Number of items in the current set.
- *
- * @property length
- * @type Number
- */
- length: 0,
- /**
- * Constructs a new DomQuery instance with the specified selector or context.
- *
- * @constructor
- * @method init
- * @param {String/Array/DomQuery} selector Optional CSS selector/Array or array like object or HTML string.
- * @param {Document/Element} context Optional context to search in.
- */
- init: function(selector, context) {
- var self = this, match, node;
- if (!selector) {
- return self;
- }
- if (selector.nodeType) {
- self.context = self[0] = selector;
- self.length = 1;
- return self;
- }
- if (context && context.nodeType) {
- self.context = context;
- } else {
- if (context) {
- return DomQuery(selector).attr(context);
- }
- self.context = context = document;
- }
- if (isString(selector)) {
- self.selector = selector;
- if (selector.charAt(0) === "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
- match = [null, selector, null];
- } else {
- match = rquickExpr.exec(selector);
- }
- if (match) {
- if (match[1]) {
- node = createFragment(selector, getElementDocument(context)).firstChild;
- while (node) {
- push.call(self, node);
- node = node.nextSibling;
- }
- } else {
- node = getElementDocument(context).getElementById(match[2]);
- if (!node) {
- return self;
- }
- if (node.id !== match[2]) {
- return self.find(selector);
- }
- self.length = 1;
- self[0] = node;
- }
- } else {
- return DomQuery(context).find(selector);
- }
- } else {
- this.add(selector, false);
- }
- return self;
- },
- /**
- * Converts the current set to an array.
- *
- * @method toArray
- * @return {Array} Array of all nodes in set.
- */
- toArray: function() {
- return Tools.toArray(this);
- },
- /**
- * Adds new nodes to the set.
- *
- * @method add
- * @param {Array/tinymce.dom.DomQuery} items Array of all nodes to add to set.
- * @param {Boolean} sort Optional sort flag that enables sorting of elements.
- * @return {tinymce.dom.DomQuery} New instance with nodes added.
- */
- add: function(items, sort) {
- var self = this, nodes, i;
- if (isString(items)) {
- return self.add(DomQuery(items));
- }
- if (sort !== false) {
- nodes = DomQuery.unique(self.toArray().concat(DomQuery.makeArray(items)));
- self.length = nodes.length;
- for (i = 0; i < nodes.length; i++) {
- self[i] = nodes[i];
- }
- } else {
- push.apply(self, DomQuery.makeArray(items));
- }
- return self;
- },
- /**
- * Sets/gets attributes on the elements in the current set.
- *
- * @method attr
- * @param {String/Object} name Name of attribute to get or an object with attributes to set.
- * @param {String} value Optional value to set.
- * @return {tinymce.dom.DomQuery/String} Current set or the specified attribute when only the name is specified.
- */
- attr: function(name, value) {
- var self = this, hook;
- if (typeof name === "object") {
- each(name, function(name, value) {
- self.attr(name, value);
- });
- } else if (isDefined(value)) {
- this.each(function() {
- var hook;
- if (this.nodeType === 1) {
- hook = attrHooks[name];
- if (hook && hook.set) {
- hook.set(this, value);
- return;
- }
- if (value === null) {
- this.removeAttribute(name, 2);
- } else {
- this.setAttribute(name, value, 2);
- }
- }
- });
- } else {
- if (self[0] && self[0].nodeType === 1) {
- hook = attrHooks[name];
- if (hook && hook.get) {
- return hook.get(self[0], name);
- }
- if (booleanMap[name]) {
- return self.prop(name) ? name : undef;
- }
- value = self[0].getAttribute(name, 2);
- if (value === null) {
- value = undef;
- }
- }
- return value;
- }
- return self;
- },
- /**
- * Removes attributse on the elements in the current set.
- *
- * @method removeAttr
- * @param {String/Object} name Name of attribute to remove.
- * @return {tinymce.dom.DomQuery/String} Current set.
- */
- removeAttr: function(name) {
- return this.attr(name, null);
- },
- /**
- * Sets/gets properties on the elements in the current set.
- *
- * @method attr
- * @param {String/Object} name Name of property to get or an object with properties to set.
- * @param {String} value Optional value to set.
- * @return {tinymce.dom.DomQuery/String} Current set or the specified property when only the name is specified.
- */
- prop: function(name, value) {
- var self = this;
- name = propFix[name] || name;
- if (typeof name === "object") {
- each(name, function(name, value) {
- self.prop(name, value);
- });
- } else if (isDefined(value)) {
- this.each(function() {
- if (this.nodeType == 1) {
- this[name] = value;
- }
- });
- } else {
- if (self[0] && self[0].nodeType && name in self[0]) {
- return self[0][name];
- }
- return value;
- }
- return self;
- },
- /**
- * Sets/gets styles on the elements in the current set.
- *
- * @method css
- * @param {String/Object} name Name of style to get or an object with styles to set.
- * @param {String} value Optional value to set.
- * @return {tinymce.dom.DomQuery/String} Current set or the specified style when only the name is specified.
- */
- css: function(name, value) {
- var self = this, elm, hook;
- function camel(name) {
- return name.replace(/-(\D)/g, function(a, b) {
- return b.toUpperCase();
- });
- }
- function dashed(name) {
- return name.replace(/[A-Z]/g, function(a) {
- return '-' + a;
- });
- }
- if (typeof name === "object") {
- each(name, function(name, value) {
- self.css(name, value);
- });
- } else {
- if (isDefined(value)) {
- name = camel(name);
- // Default px suffix on these
- if (typeof value === 'number' && !numericCssMap[name]) {
- value += 'px';
- }
- self.each(function() {
- var style = this.style;
- hook = cssHooks[name];
- if (hook && hook.set) {
- hook.set(this, value);
- return;
- }
- try {
- this.style[cssFix[name] || name] = value;
- } catch (ex) {
- // Ignore
- }
- if (value === null || value === '') {
- if (style.removeProperty) {
- style.removeProperty(dashed(name));
- } else {
- style.removeAttribute(name);
- }
- }
- });
- } else {
- elm = self[0];
- hook = cssHooks[name];
- if (hook && hook.get) {
- return hook.get(elm);
- }
- if (elm.ownerDocument.defaultView) {
- try {
- return elm.ownerDocument.defaultView.getComputedStyle(elm, null).getPropertyValue(dashed(name));
- } catch (ex) {
- return undef;
- }
- } else if (elm.currentStyle) {
- return elm.currentStyle[camel(name)];
- }
- }
- }
- return self;
- },
- /**
- * Removes all nodes in set from the document.
- *
- * @method remove
- * @return {tinymce.dom.DomQuery} Current set with the removed nodes.
- */
- remove: function() {
- var self = this, node, i = this.length;
- while (i--) {
- node = self[i];
- Event.clean(node);
- if (node.parentNode) {
- node.parentNode.removeChild(node);
- }
- }
- return this;
- },
- /**
- * Empties all elements in set.
- *
- * @method empty
- * @return {tinymce.dom.DomQuery} Current set with the empty nodes.
- */
- empty: function() {
- var self = this, node, i = this.length;
- while (i--) {
- node = self[i];
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- }
- return this;
- },
- /**
- * Sets or gets the HTML of the current set or first set node.
- *
- * @method html
- * @param {String} value Optional innerHTML value to set on each element.
- * @return {tinymce.dom.DomQuery/String} Current set or the innerHTML of the first element.
- */
- html: function(value) {
- var self = this, i;
- if (isDefined(value)) {
- i = self.length;
- try {
- while (i--) {
- self[i].innerHTML = value;
- }
- } catch (ex) {
- // Workaround for "Unknown runtime error" when DIV is added to P on IE
- DomQuery(self[i]).empty().append(value);
- }
- return self;
- }
- return self[0] ? self[0].innerHTML : '';
- },
- /**
- * Sets or gets the text of the current set or first set node.
- *
- * @method text
- * @param {String} value Optional innerText value to set on each element.
- * @return {tinymce.dom.DomQuery/String} Current set or the innerText of the first element.
- */
- text: function(value) {
- var self = this, i;
- if (isDefined(value)) {
- i = self.length;
- while (i--) {
- if ("innerText" in self[i]) {
- self[i].innerText = value;
- } else {
- self[0].textContent = value;
- }
- }
- return self;
- }
- return self[0] ? (self[0].innerText || self[0].textContent) : '';
- },
- /**
- * Appends the specified node/html or node set to the current set nodes.
- *
- * @method append
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to append to each element in set.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- append: function() {
- return domManipulate(this, arguments, function(node) {
- // Either element or Shadow Root
- if (this.nodeType === 1 || (this.host && this.host.nodeType === 1)) {
- this.appendChild(node);
- }
- });
- },
- /**
- * Prepends the specified node/html or node set to the current set nodes.
- *
- * @method prepend
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to prepend to each element in set.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- prepend: function() {
- return domManipulate(this, arguments, function(node) {
- // Either element or Shadow Root
- if (this.nodeType === 1 || (this.host && this.host.nodeType === 1)) {
- this.insertBefore(node, this.firstChild);
- }
- }, true);
- },
- /**
- * Adds the specified elements before current set nodes.
- *
- * @method before
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add before to each element in set.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- before: function() {
- var self = this;
- if (self[0] && self[0].parentNode) {
- return domManipulate(self, arguments, function(node) {
- this.parentNode.insertBefore(node, this);
- });
- }
- return self;
- },
- /**
- * Adds the specified elements after current set nodes.
- *
- * @method after
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to add after to each element in set.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- after: function() {
- var self = this;
- if (self[0] && self[0].parentNode) {
- return domManipulate(self, arguments, function(node) {
- this.parentNode.insertBefore(node, this.nextSibling);
- }, true);
- }
- return self;
- },
- /**
- * Appends the specified set nodes to the specified selector/instance.
- *
- * @method appendTo
- * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to append the current set to.
- * @return {tinymce.dom.DomQuery} Current set with the appended nodes.
- */
- appendTo: function(val) {
- DomQuery(val).append(this);
- return this;
- },
- /**
- * Prepends the specified set nodes to the specified selector/instance.
- *
- * @method prependTo
- * @param {String/Element/Array/tinymce.dom.DomQuery} val Item to prepend the current set to.
- * @return {tinymce.dom.DomQuery} Current set with the prepended nodes.
- */
- prependTo: function(val) {
- DomQuery(val).prepend(this);
- return this;
- },
- /**
- * Replaces the nodes in set with the specified content.
- *
- * @method replaceWith
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to replace nodes with.
- * @return {tinymce.dom.DomQuery} Set with replaced nodes.
- */
- replaceWith: function(content) {
- return this.before(content).remove();
- },
- /**
- * Wraps all elements in set with the specified wrapper.
- *
- * @method wrap
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
- * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
- */
- wrap: function(content) {
- return wrap(this, content);
- },
- /**
- * Wraps all nodes in set with the specified wrapper. If the nodes are siblings all of them
- * will be wrapped in the same wrapper.
- *
- * @method wrapAll
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
- * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
- */
- wrapAll: function(content) {
- return wrap(this, content, true);
- },
- /**
- * Wraps all elements inner contents in set with the specified wrapper.
- *
- * @method wrapInner
- * @param {String/Element/Array/tinymce.dom.DomQuery} content Content to wrap nodes with.
- * @return {tinymce.dom.DomQuery} Set with wrapped nodes.
- */
- wrapInner: function(content) {
- this.each(function() {
- DomQuery(this).contents().wrapAll(content);
- });
- return this;
- },
- /**
- * Unwraps all elements by removing the parent element of each item in set.
- *
- * @method unwrap
- * @return {tinymce.dom.DomQuery} Set with unwrapped nodes.
- */
- unwrap: function() {
- return this.parent().each(function() {
- DomQuery(this).replaceWith(this.childNodes);
- });
- },
- /**
- * Clones all nodes in set.
- *
- * @method clone
- * @return {tinymce.dom.DomQuery} Set with cloned nodes.
- */
- clone: function() {
- var result = [];
- this.each(function() {
- result.push(this.cloneNode(true));
- });
- return DomQuery(result);
- },
- /**
- * Adds the specified class name to the current set elements.
- *
- * @method addClass
- * @param {String} className Class name to add.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- addClass: function(className) {
- return this.toggleClass(className, true);
- },
- /**
- * Removes the specified class name to the current set elements.
- *
- * @method removeClass
- * @param {String} className Class name to remove.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- removeClass: function(className) {
- return this.toggleClass(className, false);
- },
- /**
- * Toggles the specified class name on the current set elements.
- *
- * @method toggleClass
- * @param {String} className Class name to add/remove.
- * @param {Boolean} state Optional state to toggle on/off.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- toggleClass: function(className, state) {
- var self = this;
- // Functions are not supported
- if (typeof className != 'string') {
- return self;
- }
- if (className.indexOf(' ') !== -1) {
- each(className.split(' '), function() {
- self.toggleClass(this, state);
- });
- } else {
- self.each(function(index, node) {
- var existingClassName, classState;
- classState = hasClass(node, className);
- if (classState !== state) {
- existingClassName = node.className;
- if (classState) {
- node.className = trim((" " + existingClassName + " ").replace(' ' + className + ' ', ' '));
- } else {
- node.className += existingClassName ? ' ' + className : className;
- }
- }
- });
- }
- return self;
- },
- /**
- * Returns true/false if the first item in set has the specified class.
- *
- * @method hasClass
- * @param {String} className Class name to check for.
- * @return {Boolean} True/false if the set has the specified class.
- */
- hasClass: function(className) {
- return hasClass(this[0], className);
- },
- /**
- * Executes the callback function for each item DomQuery collection. If you return false in the
- * callback it will break the loop.
- *
- * @method each
- * @param {function} callback Callback function to execute for each item.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- each: function(callback) {
- return each(this, callback);
- },
- /**
- * Binds an event with callback function to the elements in set.
- *
- * @method on
- * @param {String} name Name of the event to bind.
- * @param {function} callback Callback function to execute when the event occurs.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- on: function(name, callback) {
- return this.each(function() {
- Event.bind(this, name, callback);
- });
- },
- /**
- * Unbinds an event with callback function to the elements in set.
- *
- * @method off
- * @param {String} name Optional name of the event to bind.
- * @param {function} callback Optional callback function to execute when the event occurs.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- off: function(name, callback) {
- return this.each(function() {
- Event.unbind(this, name, callback);
- });
- },
- /**
- * Triggers the specified event by name or event object.
- *
- * @method trigger
- * @param {String/Object} name Name of the event to trigger or event object.
- * @return {tinymce.dom.DomQuery} Current set.
- */
- trigger: function(name) {
- return this.each(function() {
- if (typeof name == 'object') {
- Event.fire(this, name.type, name);
- } else {
- Event.fire(this, name);
- }
- });
- },
- /**
- * Shows all elements in set.
- *
- * @method show
- * @return {tinymce.dom.DomQuery} Current set.
- */
- show: function() {
- return this.css('display', '');
- },
- /**
- * Hides all elements in set.
- *
- * @method hide
- * @return {tinymce.dom.DomQuery} Current set.
- */
- hide: function() {
- return this.css('display', 'none');
- },
- /**
- * Slices the current set.
- *
- * @method slice
- * @param {Number} start Start index to slice at.
- * @param {Number} end Optional end index to end slice at.
- * @return {tinymce.dom.DomQuery} Sliced set.
- */
- slice: function() {
- return new DomQuery(slice.apply(this, arguments));
- },
- /**
- * Makes the set equal to the specified index.
- *
- * @method eq
- * @param {Number} index Index to set it equal to.
- * @return {tinymce.dom.DomQuery} Single item set.
- */
- eq: function(index) {
- return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
- },
- /**
- * Makes the set equal to first element in set.
- *
- * @method first
- * @return {tinymce.dom.DomQuery} Single item set.
- */
- first: function() {
- return this.eq(0);
- },
- /**
- * Makes the set equal to last element in set.
- *
- * @method last
- * @return {tinymce.dom.DomQuery} Single item set.
- */
- last: function() {
- return this.eq(-1);
- },
- /**
- * Finds elements by the specified selector for each element in set.
- *
- * @method find
- * @param {String} selector Selector to find elements by.
- * @return {tinymce.dom.DomQuery} Set with matches elements.
- */
- find: function(selector) {
- var i, l, ret = [];
- for (i = 0, l = this.length; i < l; i++) {
- DomQuery.find(selector, this[i], ret);
- }
- return DomQuery(ret);
- },
- /**
- * Filters the current set with the specified selector.
- *
- * @method filter
- * @param {String/function} selector Selector to filter elements by.
- * @return {tinymce.dom.DomQuery} Set with filtered elements.
- */
- filter: function(selector) {
- if (typeof selector == 'function') {
- return DomQuery(grep(this.toArray(), function(item, i) {
- return selector(i, item);
- }));
- }
- return DomQuery(DomQuery.filter(selector, this.toArray()));
- },
- /**
- * Gets the current node or any parent matching the specified selector.
- *
- * @method closest
- * @param {String/Element/tinymce.dom.DomQuery} selector Selector or element to find.
- * @return {tinymce.dom.DomQuery} Set with closest elements.
- */
- closest: function(selector) {
- var result = [];
- if (selector instanceof DomQuery) {
- selector = selector[0];
- }
- this.each(function(i, node) {
- while (node) {
- if (typeof selector == 'string' && DomQuery(node).is(selector)) {
- result.push(node);
- break;
- } else if (node == selector) {
- result.push(node);
- break;
- }
- node = node.parentNode;
- }
- });
- return DomQuery(result);
- },
- /**
- * Returns the offset of the first element in set or sets the top/left css properties of all elements in set.
- *
- * @method offset
- * @param {Object} offset Optional offset object to set on each item.
- * @return {Object/tinymce.dom.DomQuery} Returns the first element offset or the current set if you specified an offset.
- */
- offset: function(offset) {
- var elm, doc, docElm;
- var x = 0, y = 0, pos;
- if (!offset) {
- elm = this[0];
- if (elm) {
- doc = elm.ownerDocument;
- docElm = doc.documentElement;
- if (elm.getBoundingClientRect) {
- pos = elm.getBoundingClientRect();
- x = pos.left + (docElm.scrollLeft || doc.body.scrollLeft) - docElm.clientLeft;
- y = pos.top + (docElm.scrollTop || doc.body.scrollTop) - docElm.clientTop;
- }
- }
- return {
- left: x,
- top: y
- };
- }
- return this.css(offset);
- },
- push: push,
- sort: [].sort,
- splice: [].splice
- };
- // Static members
- Tools.extend(DomQuery, {
- /**
- * Extends the specified object with one or more objects.
- *
- * @static
- * @method extend
- * @param {Object} target Target object to extend with new items.
- * @param {Object..} object Object to extend the target with.
- * @return {Object} Extended input object.
- */
- extend: Tools.extend,
- /**
- * Creates an array out of an array like object.
- *
- * @static
- * @method makeArray
- * @param {Object} object Object to convert to array.
- * @return {Array} Array produced from object.
- */
- makeArray: function(object) {
- if (isWindow(object) || object.nodeType) {
- return [object];
- }
- return Tools.toArray(object);
- },
- /**
- * Returns the index of the specified item inside the array.
- *
- * @static
- * @method inArray
- * @param {Object} item Item to look for.
- * @param {Array} array Array to look for item in.
- * @return {Number} Index of the item or -1.
- */
- inArray: inArray,
- /**
- * Returns true/false if the specified object is an array or not.
- *
- * @static
- * @method isArray
- * @param {Object} array Object to check if it's an array or not.
- * @return {Boolean} True/false if the object is an array.
- */
- isArray: Tools.isArray,
- /**
- * Executes the callback function for each item in array/object. If you return false in the
- * callback it will break the loop.
- *
- * @static
- * @method each
- * @param {Object} obj Object to iterate.
- * @param {function} callback Callback function to execute for each item.
- */
- each: each,
- /**
- * Removes whitespace from the beginning and end of a string.
- *
- * @static
- * @method trim
- * @param {String} str String to remove whitespace from.
- * @return {String} New string with removed whitespace.
- */
- trim: trim,
- /**
- * Filters out items from the input array by calling the specified function for each item.
- * If the function returns false the item will be excluded if it returns true it will be included.
- *
- * @static
- * @method grep
- * @param {Array} array Array of items to loop though.
- * @param {function} callback Function to call for each item. Include/exclude depends on it's return value.
- * @return {Array} New array with values imported and filtered based in input.
- * @example
- * // Filter out some items, this will return an array with 4 and 5
- * var items = DomQuery.grep([1, 2, 3, 4, 5], function(v) {return v > 3;});
- */
- grep: grep,
- // Sizzle
- find: Sizzle,
- expr: Sizzle.selectors,
- unique: Sizzle.uniqueSort,
- text: Sizzle.getText,
- contains: Sizzle.contains,
- filter: function(expr, elems, not) {
- var i = elems.length;
- if (not) {
- expr = ":not(" + expr + ")";
- }
- while (i--) {
- if (elems[i].nodeType != 1) {
- elems.splice(i, 1);
- }
- }
- if (elems.length === 1) {
- elems = DomQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [];
- } else {
- elems = DomQuery.find.matches(expr, elems);
- }
- return elems;
- }
- });
- function dir(el, prop, until) {
- var matched = [], cur = el[prop];
- if (typeof until != 'string' && until instanceof DomQuery) {
- until = until[0];
- }
- while (cur && cur.nodeType !== 9) {
- if (until !== undefined) {
- if (cur === until) {
- break;
- }
- if (typeof until == 'string' && DomQuery(cur).is(until)) {
- break;
- }
- }
- if (cur.nodeType === 1) {
- matched.push(cur);
- }
- cur = cur[prop];
- }
- return matched;
- }
- function sibling(node, siblingName, nodeType, until) {
- var result = [];
- if (until instanceof DomQuery) {
- until = until[0];
- }
- for (; node; node = node[siblingName]) {
- if (nodeType && node.nodeType !== nodeType) {
- continue;
- }
- if (until !== undefined) {
- if (node === until) {
- break;
- }
- if (typeof until == 'string' && DomQuery(node).is(until)) {
- break;
- }
- }
- result.push(node);
- }
- return result;
- }
- function firstSibling(node, siblingName, nodeType) {
- for (node = node[siblingName]; node; node = node[siblingName]) {
- if (node.nodeType == nodeType) {
- return node;
- }
- }
- return null;
- }
- each({
- /**
- * Returns a new collection with the parent of each item in current collection matching the optional selector.
- *
- * @method parent
- * @param {Element/tinymce.dom.DomQuery} node Node to match parents against.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
- */
- parent: function(node) {
- var parent = node.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
- },
- /**
- * Returns a new collection with the all the parents of each item in current collection matching the optional selector.
- *
- * @method parents
- * @param {Element/tinymce.dom.DomQuery} node Node to match parents against.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
- */
- parents: function(node) {
- return dir(node, "parentNode");
- },
- /**
- * Returns a new collection with next sibling of each item in current collection matching the optional selector.
- *
- * @method next
- * @param {Element/tinymce.dom.DomQuery} node Node to match the next element against.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
- */
- next: function(node) {
- return firstSibling(node, 'nextSibling', 1);
- },
- /**
- * Returns a new collection with previous sibling of each item in current collection matching the optional selector.
- *
- * @method prev
- * @param {Element/tinymce.dom.DomQuery} node Node to match the previous element against.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
- */
- prev: function(node) {
- return firstSibling(node, 'previousSibling', 1);
- },
- /**
- * Returns all child elements matching the optional selector.
- *
- * @method children
- * @param {Element/tinymce.dom.DomQuery} node Node to match the elements against.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
- */
- children: function(node) {
- return sibling(node.firstChild, 'nextSibling', 1);
- },
- /**
- * Returns all child nodes matching the optional selector.
- *
- * @method contents
- * @param {Element/tinymce.dom.DomQuery} node Node to get the contents of.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
- */
- contents: function(node) {
- return Tools.toArray((node.nodeName === "iframe" ? node.contentDocument || node.contentWindow.document : node).childNodes);
- }
- }, function(name, fn) {
- DomQuery.fn[name] = function(selector) {
- var self = this, result = [];
- self.each(function() {
- var nodes = fn.call(result, this, selector, result);
- if (nodes) {
- if (DomQuery.isArray(nodes)) {
- result.push.apply(result, nodes);
- } else {
- result.push(nodes);
- }
- }
- });
- // If traversing on multiple elements we might get the same elements twice
- if (this.length > 1) {
- if (!skipUniques[name]) {
- result = DomQuery.unique(result);
- }
- if (name.indexOf('parents') === 0) {
- result = result.reverse();
- }
- }
- result = DomQuery(result);
- if (selector) {
- return result.filter(selector);
- }
- return result;
- };
- });
- each({
- /**
- * Returns a new collection with the all the parents until the matching selector/element
- * of each item in current collection matching the optional selector.
- *
- * @method parentsUntil
- * @param {Element/tinymce.dom.DomQuery} node Node to find parent of.
- * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching parents.
- */
- parentsUntil: function(node, until) {
- return dir(node, "parentNode", until);
- },
- /**
- * Returns a new collection with all next siblings of each item in current collection matching the optional selector.
- *
- * @method nextUntil
- * @param {Element/tinymce.dom.DomQuery} node Node to find next siblings on.
- * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
- */
- nextUntil: function(node, until) {
- return sibling(node, 'nextSibling', 1, until).slice(1);
- },
- /**
- * Returns a new collection with all previous siblings of each item in current collection matching the optional selector.
- *
- * @method prevUntil
- * @param {Element/tinymce.dom.DomQuery} node Node to find previous siblings on.
- * @param {String/Element/tinymce.dom.DomQuery} until Until the matching selector or element.
- * @return {tinymce.dom.DomQuery} New DomQuery instance with all matching elements.
- */
- prevUntil: function(node, until) {
- return sibling(node, 'previousSibling', 1, until).slice(1);
- }
- }, function(name, fn) {
- DomQuery.fn[name] = function(selector, filter) {
- var self = this, result = [];
- self.each(function() {
- var nodes = fn.call(result, this, selector, result);
- if (nodes) {
- if (DomQuery.isArray(nodes)) {
- result.push.apply(result, nodes);
- } else {
- result.push(nodes);
- }
- }
- });
- // If traversing on multiple elements we might get the same elements twice
- if (this.length > 1) {
- result = DomQuery.unique(result);
- if (name.indexOf('parents') === 0 || name === 'prevUntil') {
- result = result.reverse();
- }
- }
- result = DomQuery(result);
- if (filter) {
- return result.filter(filter);
- }
- return result;
- };
- });
- /**
- * Returns true/false if the current set items matches the selector.
- *
- * @method is
- * @param {String} selector Selector to match the elements against.
- * @return {Boolean} True/false if the current set matches the selector.
- */
- DomQuery.fn.is = function(selector) {
- return !!selector && this.filter(selector).length > 0;
- };
- DomQuery.fn.init.prototype = DomQuery.fn;
- DomQuery.overrideDefaults = function(callback) {
- var defaults;
- function sub(selector, context) {
- defaults = defaults || callback();
- if (arguments.length === 0) {
- selector = defaults.element;
- }
- if (!context) {
- context = defaults.context;
- }
- return new sub.fn.init(selector, context);
- }
- DomQuery.extend(sub, this);
- return sub;
- };
- function appendHooks(targetHooks, prop, hooks) {
- each(hooks, function(name, func) {
- targetHooks[name] = targetHooks[name] || {};
- targetHooks[name][prop] = func;
- });
- }
- if (Env.ie && Env.ie < 8) {
- appendHooks(attrHooks, 'get', {
- maxlength: function(elm) {
- var value = elm.maxLength;
- if (value === 0x7fffffff) {
- return undef;
- }
- return value;
- },
- size: function(elm) {
- var value = elm.size;
- if (value === 20) {
- return undef;
- }
- return value;
- },
- 'class': function(elm) {
- return elm.className;
- },
- style: function(elm) {
- var value = elm.style.cssText;
- if (value.length === 0) {
- return undef;
- }
- return value;
- }
- });
- appendHooks(attrHooks, 'set', {
- 'class': function(elm, value) {
- elm.className = value;
- },
- style: function(elm, value) {
- elm.style.cssText = value;
- }
- });
- }
- if (Env.ie && Env.ie < 9) {
- /*jshint sub:true */
- /*eslint dot-notation: 0*/
- cssFix['float'] = 'styleFloat';
- appendHooks(cssHooks, 'set', {
- opacity: function(elm, value) {
- var style = elm.style;
- if (value === null || value === '') {
- style.removeAttribute('filter');
- } else {
- style.zoom = 1;
- style.filter = 'alpha(opacity=' + (value * 100) + ')';
- }
- }
- });
- }
- DomQuery.attrHooks = attrHooks;
- DomQuery.cssHooks = cssHooks;
- return DomQuery;
- });
- // Included from: js/tinymce/classes/html/Styles.js
- /**
- * Styles.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is used to parse CSS styles it also compresses styles to reduce the output size.
- *
- * @example
- * var Styles = new tinymce.html.Styles({
- * url_converter: function(url) {
- * return url;
- * }
- * });
- *
- * styles = Styles.parse('border: 1px solid red');
- * styles.color = 'red';
- *
- * console.log(new tinymce.html.StyleSerializer().serialize(styles));
- *
- * @class tinymce.html.Styles
- * @version 3.4
- */
- define("tinymce/html/Styles", [], function() {
- return function(settings, schema) {
- /*jshint maxlen:255 */
- /*eslint max-len:0 */
- var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
- urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
- styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
- trimRightRegExp = /\s+$/,
- undef, i, encodingLookup = {}, encodingItems, validStyles, invalidStyles, invisibleChar = '\uFEFF';
- settings = settings || {};
- if (schema) {
- validStyles = schema.getValidStyles();
- invalidStyles = schema.getInvalidStyles();
- }
- encodingItems = ('\\" \\\' \\; \\: ; : ' + invisibleChar).split(' ');
- for (i = 0; i < encodingItems.length; i++) {
- encodingLookup[encodingItems[i]] = invisibleChar + i;
- encodingLookup[invisibleChar + i] = encodingItems[i];
- }
- function toHex(match, r, g, b) {
- function hex(val) {
- val = parseInt(val, 10).toString(16);
- return val.length > 1 ? val : '0' + val; // 0 -> 00
- }
- return '#' + hex(r) + hex(g) + hex(b);
- }
- return {
- /**
- * Parses the specified RGB color value and returns a hex version of that color.
- *
- * @method toHex
- * @param {String} color RGB string value like rgb(1,2,3)
- * @return {String} Hex version of that RGB value like #FF00FF.
- */
- toHex: function(color) {
- return color.replace(rgbRegExp, toHex);
- },
- /**
- * Parses the specified style value into an object collection. This parser will also
- * merge and remove any redundant items that browsers might have added. It will also convert non hex
- * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
- *
- * @method parse
- * @param {String} css Style value to parse for example: border:1px solid red;.
- * @return {Object} Object representation of that style like {border: '1px solid red'}
- */
- parse: function(css) {
- var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter;
- var urlConverterScope = settings.url_converter_scope || this;
- function compress(prefix, suffix, noJoin) {
- var top, right, bottom, left;
- top = styles[prefix + '-top' + suffix];
- if (!top) {
- return;
- }
- right = styles[prefix + '-right' + suffix];
- if (!right) {
- return;
- }
- bottom = styles[prefix + '-bottom' + suffix];
- if (!bottom) {
- return;
- }
- left = styles[prefix + '-left' + suffix];
- if (!left) {
- return;
- }
- var box = [top, right, bottom, left];
- i = box.length - 1;
- while (i--) {
- if (box[i] !== box[i + 1]) {
- break;
- }
- }
- if (i > -1 && noJoin) {
- return;
- }
- styles[prefix + suffix] = i == -1 ? box[0] : box.join(' ');
- delete styles[prefix + '-top' + suffix];
- delete styles[prefix + '-right' + suffix];
- delete styles[prefix + '-bottom' + suffix];
- delete styles[prefix + '-left' + suffix];
- }
- /**
- * Checks if the specific style can be compressed in other words if all border-width are equal.
- */
- function canCompress(key) {
- var value = styles[key], i;
- if (!value) {
- return;
- }
- value = value.split(' ');
- i = value.length;
- while (i--) {
- if (value[i] !== value[0]) {
- return false;
- }
- }
- styles[key] = value[0];
- return true;
- }
- /**
- * Compresses multiple styles into one style.
- */
- function compress2(target, a, b, c) {
- if (!canCompress(a)) {
- return;
- }
- if (!canCompress(b)) {
- return;
- }
- if (!canCompress(c)) {
- return;
- }
- // Compress
- styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
- delete styles[a];
- delete styles[b];
- delete styles[c];
- }
- // Encodes the specified string by replacing all \" \' ; : with _<num>
- function encode(str) {
- isEncoded = true;
- return encodingLookup[str];
- }
- // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
- // It will also decode the \" \' if keep_slashes is set to fale or omitted
- function decode(str, keep_slashes) {
- if (isEncoded) {
- str = str.replace(/\uFEFF[0-9]/g, function(str) {
- return encodingLookup[str];
- });
- }
- if (!keep_slashes) {
- str = str.replace(/\\([\'\";:])/g, "$1");
- }
- return str;
- }
- function processUrl(match, url, url2, url3, str, str2) {
- str = str || str2;
- if (str) {
- str = decode(str);
- // Force strings into single quote format
- return "'" + str.replace(/\'/g, "\\'") + "'";
- }
- url = decode(url || url2 || url3);
- if (!settings.allow_script_urls) {
- var scriptUrl = url.replace(/[\s\r\n]+/, '');
- if (/(java|vb)script:/i.test(scriptUrl)) {
- return "";
- }
- if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
- return "";
- }
- }
- // Convert the URL to relative/absolute depending on config
- if (urlConverter) {
- url = urlConverter.call(urlConverterScope, url, 'style');
- }
- // Output new URL format
- return "url('" + url.replace(/\'/g, "\\'") + "')";
- }
- if (css) {
- css = css.replace(/[\u0000-\u001F]/g, '');
- // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
- css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
- return str.replace(/[;:]/g, encode);
- });
- // Parse styles
- while ((matches = styleRegExp.exec(css))) {
- name = matches[1].replace(trimRightRegExp, '').toLowerCase();
- value = matches[2].replace(trimRightRegExp, '');
- // Decode escaped sequences like \65 -> e
- /*jshint loopfunc:true*/
- /*eslint no-loop-func:0 */
- value = value.replace(/\\[0-9a-f]+/g, function(e) {
- return String.fromCharCode(parseInt(e.substr(1), 16));
- });
- if (name && value.length > 0) {
- // Don't allow behavior name or expression/comments within the values
- if (!settings.allow_script_urls && (name == "behavior" || /expression\s*\(|\/\*|\*\//.test(value))) {
- continue;
- }
- // Opera will produce 700 instead of bold in their style values
- if (name === 'font-weight' && value === '700') {
- value = 'bold';
- } else if (name === 'color' || name === 'background-color') { // Lowercase colors like RED
- value = value.toLowerCase();
- }
- // Convert RGB colors to HEX
- value = value.replace(rgbRegExp, toHex);
- // Convert URLs and force them into url('value') format
- value = value.replace(urlOrStrRegExp, processUrl);
- styles[name] = isEncoded ? decode(value, true) : value;
- }
- styleRegExp.lastIndex = matches.index + matches[0].length;
- }
- // Compress the styles to reduce it's size for example IE will expand styles
- compress("border", "", true);
- compress("border", "-width");
- compress("border", "-color");
- compress("border", "-style");
- compress("padding", "");
- compress("margin", "");
- compress2('border', 'border-width', 'border-style', 'border-color');
- // Remove pointless border, IE produces these
- if (styles.border === 'medium none') {
- delete styles.border;
- }
- // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
- // So let us assume it shouldn't be there
- if (styles['border-image'] === 'none') {
- delete styles['border-image'];
- }
- }
- return styles;
- },
- /**
- * Serializes the specified style object into a string.
- *
- * @method serialize
- * @param {Object} styles Object to serialize as string for example: {border: '1px solid red'}
- * @param {String} elementName Optional element name, if specified only the styles that matches the schema will be serialized.
- * @return {String} String representation of the style object for example: border: 1px solid red.
- */
- serialize: function(styles, elementName) {
- var css = '', name, value;
- function serializeStyles(name) {
- var styleList, i, l, value;
- styleList = validStyles[name];
- if (styleList) {
- for (i = 0, l = styleList.length; i < l; i++) {
- name = styleList[i];
- value = styles[name];
- if (value !== undef && value.length > 0) {
- css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
- }
- }
- }
- }
- function isValid(name, elementName) {
- var styleMap;
- styleMap = invalidStyles['*'];
- if (styleMap && styleMap[name]) {
- return false;
- }
- styleMap = invalidStyles[elementName];
- if (styleMap && styleMap[name]) {
- return false;
- }
- return true;
- }
- // Serialize styles according to schema
- if (elementName && validStyles) {
- // Serialize global styles and element specific styles
- serializeStyles('*');
- serializeStyles(elementName);
- } else {
- // Output the styles in the order they are inside the object
- for (name in styles) {
- value = styles[name];
- if (value !== undef && value.length > 0) {
- if (!invalidStyles || isValid(name, elementName)) {
- css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
- }
- }
- }
- }
- return css;
- }
- };
- };
- });
- // Included from: js/tinymce/classes/dom/TreeWalker.js
- /**
- * TreeWalker.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * TreeWalker class enables you to walk the DOM in a linear manner.
- *
- * @class tinymce.dom.TreeWalker
- * @example
- * var walker = new tinymce.dom.TreeWalker(startNode);
- *
- * do {
- * console.log(walker.current());
- * } while (walker.next());
- */
- define("tinymce/dom/TreeWalker", [], function() {
- /**
- * Constructs a new TreeWalker instance.
- *
- * @constructor
- * @method TreeWalker
- * @param {Node} startNode Node to start walking from.
- * @param {node} rootNode Optional root node to never walk out of.
- */
- return function(startNode, rootNode) {
- var node = startNode;
- function findSibling(node, startName, siblingName, shallow) {
- var sibling, parent;
- if (node) {
- // Walk into nodes if it has a start
- if (!shallow && node[startName]) {
- return node[startName];
- }
- // Return the sibling if it has one
- if (node != rootNode) {
- sibling = node[siblingName];
- if (sibling) {
- return sibling;
- }
- // Walk up the parents to look for siblings
- for (parent = node.parentNode; parent && parent != rootNode; parent = parent.parentNode) {
- sibling = parent[siblingName];
- if (sibling) {
- return sibling;
- }
- }
- }
- }
- }
- function findPreviousNode(node, startName, siblingName, shallow) {
- var sibling, parent, child;
- if (node) {
- sibling = node[siblingName];
- if (rootNode && sibling === rootNode) {
- return;
- }
- if (sibling) {
- if (!shallow) {
- // Walk up the parents to look for siblings
- for (child = sibling[startName]; child; child = child[startName]) {
- if (!child[startName]) {
- return child;
- }
- }
- }
- return sibling;
- }
- parent = node.parentNode;
- if (parent && parent !== rootNode) {
- return parent;
- }
- }
- }
- /**
- * Returns the current node.
- *
- * @method current
- * @return {Node} Current node where the walker is.
- */
- this.current = function() {
- return node;
- };
- /**
- * Walks to the next node in tree.
- *
- * @method next
- * @return {Node} Current node where the walker is after moving to the next node.
- */
- this.next = function(shallow) {
- node = findSibling(node, 'firstChild', 'nextSibling', shallow);
- return node;
- };
- /**
- * Walks to the previous node in tree.
- *
- * @method prev
- * @return {Node} Current node where the walker is after moving to the previous node.
- */
- this.prev = function(shallow) {
- node = findSibling(node, 'lastChild', 'previousSibling', shallow);
- return node;
- };
- this.prev2 = function(shallow) {
- node = findPreviousNode(node, 'lastChild', 'previousSibling', shallow);
- return node;
- };
- };
- });
- // Included from: js/tinymce/classes/dom/Range.js
- /**
- * Range.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Old IE Range.
- *
- * @private
- * @class tinymce.dom.Range
- */
- define("tinymce/dom/Range", [
- "tinymce/util/Tools"
- ], function(Tools) {
- // Range constructor
- function Range(dom) {
- var self = this,
- doc = dom.doc,
- EXTRACT = 0,
- CLONE = 1,
- DELETE = 2,
- TRUE = true,
- FALSE = false,
- START_OFFSET = 'startOffset',
- START_CONTAINER = 'startContainer',
- END_CONTAINER = 'endContainer',
- END_OFFSET = 'endOffset',
- extend = Tools.extend,
- nodeIndex = dom.nodeIndex;
- function createDocumentFragment() {
- return doc.createDocumentFragment();
- }
- function setStart(n, o) {
- _setEndPoint(TRUE, n, o);
- }
- function setEnd(n, o) {
- _setEndPoint(FALSE, n, o);
- }
- function setStartBefore(n) {
- setStart(n.parentNode, nodeIndex(n));
- }
- function setStartAfter(n) {
- setStart(n.parentNode, nodeIndex(n) + 1);
- }
- function setEndBefore(n) {
- setEnd(n.parentNode, nodeIndex(n));
- }
- function setEndAfter(n) {
- setEnd(n.parentNode, nodeIndex(n) + 1);
- }
- function collapse(ts) {
- if (ts) {
- self[END_CONTAINER] = self[START_CONTAINER];
- self[END_OFFSET] = self[START_OFFSET];
- } else {
- self[START_CONTAINER] = self[END_CONTAINER];
- self[START_OFFSET] = self[END_OFFSET];
- }
- self.collapsed = TRUE;
- }
- function selectNode(n) {
- setStartBefore(n);
- setEndAfter(n);
- }
- function selectNodeContents(n) {
- setStart(n, 0);
- setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
- }
- function compareBoundaryPoints(h, r) {
- var sc = self[START_CONTAINER], so = self[START_OFFSET], ec = self[END_CONTAINER], eo = self[END_OFFSET],
- rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
- // Check START_TO_START
- if (h === 0) {
- return _compareBoundaryPoints(sc, so, rsc, rso);
- }
- // Check START_TO_END
- if (h === 1) {
- return _compareBoundaryPoints(ec, eo, rsc, rso);
- }
- // Check END_TO_END
- if (h === 2) {
- return _compareBoundaryPoints(ec, eo, rec, reo);
- }
- // Check END_TO_START
- if (h === 3) {
- return _compareBoundaryPoints(sc, so, rec, reo);
- }
- }
- function deleteContents() {
- _traverse(DELETE);
- }
- function extractContents() {
- return _traverse(EXTRACT);
- }
- function cloneContents() {
- return _traverse(CLONE);
- }
- function insertNode(n) {
- var startContainer = this[START_CONTAINER],
- startOffset = this[START_OFFSET], nn, o;
- // Node is TEXT_NODE or CDATA
- if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
- if (!startOffset) {
- // At the start of text
- startContainer.parentNode.insertBefore(n, startContainer);
- } else if (startOffset >= startContainer.nodeValue.length) {
- // At the end of text
- dom.insertAfter(n, startContainer);
- } else {
- // Middle, need to split
- nn = startContainer.splitText(startOffset);
- startContainer.parentNode.insertBefore(n, nn);
- }
- } else {
- // Insert element node
- if (startContainer.childNodes.length > 0) {
- o = startContainer.childNodes[startOffset];
- }
- if (o) {
- startContainer.insertBefore(n, o);
- } else {
- if (startContainer.nodeType == 3) {
- dom.insertAfter(n, startContainer);
- } else {
- startContainer.appendChild(n);
- }
- }
- }
- }
- function surroundContents(n) {
- var f = self.extractContents();
- self.insertNode(n);
- n.appendChild(f);
- self.selectNode(n);
- }
- function cloneRange() {
- return extend(new Range(dom), {
- startContainer: self[START_CONTAINER],
- startOffset: self[START_OFFSET],
- endContainer: self[END_CONTAINER],
- endOffset: self[END_OFFSET],
- collapsed: self.collapsed,
- commonAncestorContainer: self.commonAncestorContainer
- });
- }
- // Private methods
- function _getSelectedNode(container, offset) {
- var child;
- // TEXT_NODE
- if (container.nodeType == 3) {
- return container;
- }
- if (offset < 0) {
- return container;
- }
- child = container.firstChild;
- while (child && offset > 0) {
- --offset;
- child = child.nextSibling;
- }
- if (child) {
- return child;
- }
- return container;
- }
- function _isCollapsed() {
- return (self[START_CONTAINER] == self[END_CONTAINER] && self[START_OFFSET] == self[END_OFFSET]);
- }
- function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
- var c, offsetC, n, cmnRoot, childA, childB;
- // In the first case the boundary-points have the same container. A is before B
- // if its offset is less than the offset of B, A is equal to B if its offset is
- // equal to the offset of B, and A is after B if its offset is greater than the
- // offset of B.
- if (containerA == containerB) {
- if (offsetA == offsetB) {
- return 0; // equal
- }
- if (offsetA < offsetB) {
- return -1; // before
- }
- return 1; // after
- }
- // In the second case a child node C of the container of A is an ancestor
- // container of B. In this case, A is before B if the offset of A is less than or
- // equal to the index of the child node C and A is after B otherwise.
- c = containerB;
- while (c && c.parentNode != containerA) {
- c = c.parentNode;
- }
- if (c) {
- offsetC = 0;
- n = containerA.firstChild;
- while (n != c && offsetC < offsetA) {
- offsetC++;
- n = n.nextSibling;
- }
- if (offsetA <= offsetC) {
- return -1; // before
- }
- return 1; // after
- }
- // In the third case a child node C of the container of B is an ancestor container
- // of A. In this case, A is before B if the index of the child node C is less than
- // the offset of B and A is after B otherwise.
- c = containerA;
- while (c && c.parentNode != containerB) {
- c = c.parentNode;
- }
- if (c) {
- offsetC = 0;
- n = containerB.firstChild;
- while (n != c && offsetC < offsetB) {
- offsetC++;
- n = n.nextSibling;
- }
- if (offsetC < offsetB) {
- return -1; // before
- }
- return 1; // after
- }
- // In the fourth case, none of three other cases hold: the containers of A and B
- // are siblings or descendants of sibling nodes. In this case, A is before B if
- // the container of A is before the container of B in a pre-order traversal of the
- // Ranges' context tree and A is after B otherwise.
- cmnRoot = dom.findCommonAncestor(containerA, containerB);
- childA = containerA;
- while (childA && childA.parentNode != cmnRoot) {
- childA = childA.parentNode;
- }
- if (!childA) {
- childA = cmnRoot;
- }
- childB = containerB;
- while (childB && childB.parentNode != cmnRoot) {
- childB = childB.parentNode;
- }
- if (!childB) {
- childB = cmnRoot;
- }
- if (childA == childB) {
- return 0; // equal
- }
- n = cmnRoot.firstChild;
- while (n) {
- if (n == childA) {
- return -1; // before
- }
- if (n == childB) {
- return 1; // after
- }
- n = n.nextSibling;
- }
- }
- function _setEndPoint(st, n, o) {
- var ec, sc;
- if (st) {
- self[START_CONTAINER] = n;
- self[START_OFFSET] = o;
- } else {
- self[END_CONTAINER] = n;
- self[END_OFFSET] = o;
- }
- // If one boundary-point of a Range is set to have a root container
- // other than the current one for the Range, the Range is collapsed to
- // the new position. This enforces the restriction that both boundary-
- // points of a Range must have the same root container.
- ec = self[END_CONTAINER];
- while (ec.parentNode) {
- ec = ec.parentNode;
- }
- sc = self[START_CONTAINER];
- while (sc.parentNode) {
- sc = sc.parentNode;
- }
- if (sc == ec) {
- // The start position of a Range is guaranteed to never be after the
- // end position. To enforce this restriction, if the start is set to
- // be at a position after the end, the Range is collapsed to that
- // position.
- if (_compareBoundaryPoints(self[START_CONTAINER], self[START_OFFSET], self[END_CONTAINER], self[END_OFFSET]) > 0) {
- self.collapse(st);
- }
- } else {
- self.collapse(st);
- }
- self.collapsed = _isCollapsed();
- self.commonAncestorContainer = dom.findCommonAncestor(self[START_CONTAINER], self[END_CONTAINER]);
- }
- function _traverse(how) {
- var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
- if (self[START_CONTAINER] == self[END_CONTAINER]) {
- return _traverseSameContainer(how);
- }
- for (c = self[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
- if (p == self[START_CONTAINER]) {
- return _traverseCommonStartContainer(c, how);
- }
- ++endContainerDepth;
- }
- for (c = self[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
- if (p == self[END_CONTAINER]) {
- return _traverseCommonEndContainer(c, how);
- }
- ++startContainerDepth;
- }
- depthDiff = startContainerDepth - endContainerDepth;
- startNode = self[START_CONTAINER];
- while (depthDiff > 0) {
- startNode = startNode.parentNode;
- depthDiff--;
- }
- endNode = self[END_CONTAINER];
- while (depthDiff < 0) {
- endNode = endNode.parentNode;
- depthDiff++;
- }
- // ascend the ancestor hierarchy until we have a common parent.
- for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
- startNode = sp;
- endNode = ep;
- }
- return _traverseCommonAncestors(startNode, endNode, how);
- }
- function _traverseSameContainer(how) {
- var frag, s, sub, n, cnt, sibling, xferNode, start, len;
- if (how != DELETE) {
- frag = createDocumentFragment();
- }
- // If selection is empty, just return the fragment
- if (self[START_OFFSET] == self[END_OFFSET]) {
- return frag;
- }
- // Text node needs special case handling
- if (self[START_CONTAINER].nodeType == 3) { // TEXT_NODE
- // get the substring
- s = self[START_CONTAINER].nodeValue;
- sub = s.substring(self[START_OFFSET], self[END_OFFSET]);
- // set the original text node to its new value
- if (how != CLONE) {
- n = self[START_CONTAINER];
- start = self[START_OFFSET];
- len = self[END_OFFSET] - self[START_OFFSET];
- if (start === 0 && len >= n.nodeValue.length - 1) {
- n.parentNode.removeChild(n);
- } else {
- n.deleteData(start, len);
- }
- // Nothing is partially selected, so collapse to start point
- self.collapse(TRUE);
- }
- if (how == DELETE) {
- return;
- }
- if (sub.length > 0) {
- frag.appendChild(doc.createTextNode(sub));
- }
- return frag;
- }
- // Copy nodes between the start/end offsets.
- n = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]);
- cnt = self[END_OFFSET] - self[START_OFFSET];
- while (n && cnt > 0) {
- sibling = n.nextSibling;
- xferNode = _traverseFullySelected(n, how);
- if (frag) {
- frag.appendChild(xferNode);
- }
- --cnt;
- n = sibling;
- }
- // Nothing is partially selected, so collapse to start point
- if (how != CLONE) {
- self.collapse(TRUE);
- }
- return frag;
- }
- function _traverseCommonStartContainer(endAncestor, how) {
- var frag, n, endIdx, cnt, sibling, xferNode;
- if (how != DELETE) {
- frag = createDocumentFragment();
- }
- n = _traverseRightBoundary(endAncestor, how);
- if (frag) {
- frag.appendChild(n);
- }
- endIdx = nodeIndex(endAncestor);
- cnt = endIdx - self[START_OFFSET];
- if (cnt <= 0) {
- // Collapse to just before the endAncestor, which
- // is partially selected.
- if (how != CLONE) {
- self.setEndBefore(endAncestor);
- self.collapse(FALSE);
- }
- return frag;
- }
- n = endAncestor.previousSibling;
- while (cnt > 0) {
- sibling = n.previousSibling;
- xferNode = _traverseFullySelected(n, how);
- if (frag) {
- frag.insertBefore(xferNode, frag.firstChild);
- }
- --cnt;
- n = sibling;
- }
- // Collapse to just before the endAncestor, which
- // is partially selected.
- if (how != CLONE) {
- self.setEndBefore(endAncestor);
- self.collapse(FALSE);
- }
- return frag;
- }
- function _traverseCommonEndContainer(startAncestor, how) {
- var frag, startIdx, n, cnt, sibling, xferNode;
- if (how != DELETE) {
- frag = createDocumentFragment();
- }
- n = _traverseLeftBoundary(startAncestor, how);
- if (frag) {
- frag.appendChild(n);
- }
- startIdx = nodeIndex(startAncestor);
- ++startIdx; // Because we already traversed it
- cnt = self[END_OFFSET] - startIdx;
- n = startAncestor.nextSibling;
- while (n && cnt > 0) {
- sibling = n.nextSibling;
- xferNode = _traverseFullySelected(n, how);
- if (frag) {
- frag.appendChild(xferNode);
- }
- --cnt;
- n = sibling;
- }
- if (how != CLONE) {
- self.setStartAfter(startAncestor);
- self.collapse(TRUE);
- }
- return frag;
- }
- function _traverseCommonAncestors(startAncestor, endAncestor, how) {
- var n, frag, startOffset, endOffset, cnt, sibling, nextSibling;
- if (how != DELETE) {
- frag = createDocumentFragment();
- }
- n = _traverseLeftBoundary(startAncestor, how);
- if (frag) {
- frag.appendChild(n);
- }
- startOffset = nodeIndex(startAncestor);
- endOffset = nodeIndex(endAncestor);
- ++startOffset;
- cnt = endOffset - startOffset;
- sibling = startAncestor.nextSibling;
- while (cnt > 0) {
- nextSibling = sibling.nextSibling;
- n = _traverseFullySelected(sibling, how);
- if (frag) {
- frag.appendChild(n);
- }
- sibling = nextSibling;
- --cnt;
- }
- n = _traverseRightBoundary(endAncestor, how);
- if (frag) {
- frag.appendChild(n);
- }
- if (how != CLONE) {
- self.setStartAfter(startAncestor);
- self.collapse(TRUE);
- }
- return frag;
- }
- function _traverseRightBoundary(root, how) {
- var next = _getSelectedNode(self[END_CONTAINER], self[END_OFFSET] - 1), parent, clonedParent;
- var prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != self[END_CONTAINER];
- if (next == root) {
- return _traverseNode(next, isFullySelected, FALSE, how);
- }
- parent = next.parentNode;
- clonedParent = _traverseNode(parent, FALSE, FALSE, how);
- while (parent) {
- while (next) {
- prevSibling = next.previousSibling;
- clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
- if (how != DELETE) {
- clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
- }
- isFullySelected = TRUE;
- next = prevSibling;
- }
- if (parent == root) {
- return clonedParent;
- }
- next = parent.previousSibling;
- parent = parent.parentNode;
- clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
- if (how != DELETE) {
- clonedGrandParent.appendChild(clonedParent);
- }
- clonedParent = clonedGrandParent;
- }
- }
- function _traverseLeftBoundary(root, how) {
- var next = _getSelectedNode(self[START_CONTAINER], self[START_OFFSET]), isFullySelected = next != self[START_CONTAINER];
- var parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
- if (next == root) {
- return _traverseNode(next, isFullySelected, TRUE, how);
- }
- parent = next.parentNode;
- clonedParent = _traverseNode(parent, FALSE, TRUE, how);
- while (parent) {
- while (next) {
- nextSibling = next.nextSibling;
- clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
- if (how != DELETE) {
- clonedParent.appendChild(clonedChild);
- }
- isFullySelected = TRUE;
- next = nextSibling;
- }
- if (parent == root) {
- return clonedParent;
- }
- next = parent.nextSibling;
- parent = parent.parentNode;
- clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
- if (how != DELETE) {
- clonedGrandParent.appendChild(clonedParent);
- }
- clonedParent = clonedGrandParent;
- }
- }
- function _traverseNode(n, isFullySelected, isLeft, how) {
- var txtValue, newNodeValue, oldNodeValue, offset, newNode;
- if (isFullySelected) {
- return _traverseFullySelected(n, how);
- }
- // TEXT_NODE
- if (n.nodeType == 3) {
- txtValue = n.nodeValue;
- if (isLeft) {
- offset = self[START_OFFSET];
- newNodeValue = txtValue.substring(offset);
- oldNodeValue = txtValue.substring(0, offset);
- } else {
- offset = self[END_OFFSET];
- newNodeValue = txtValue.substring(0, offset);
- oldNodeValue = txtValue.substring(offset);
- }
- if (how != CLONE) {
- n.nodeValue = oldNodeValue;
- }
- if (how == DELETE) {
- return;
- }
- newNode = dom.clone(n, FALSE);
- newNode.nodeValue = newNodeValue;
- return newNode;
- }
- if (how == DELETE) {
- return;
- }
- return dom.clone(n, FALSE);
- }
- function _traverseFullySelected(n, how) {
- if (how != DELETE) {
- return how == CLONE ? dom.clone(n, TRUE) : n;
- }
- n.parentNode.removeChild(n);
- }
- function toStringIE() {
- return dom.create('body', null, cloneContents()).outerText;
- }
- extend(self, {
- // Initial states
- startContainer: doc,
- startOffset: 0,
- endContainer: doc,
- endOffset: 0,
- collapsed: TRUE,
- commonAncestorContainer: doc,
- // Range constants
- START_TO_START: 0,
- START_TO_END: 1,
- END_TO_END: 2,
- END_TO_START: 3,
- // Public methods
- setStart: setStart,
- setEnd: setEnd,
- setStartBefore: setStartBefore,
- setStartAfter: setStartAfter,
- setEndBefore: setEndBefore,
- setEndAfter: setEndAfter,
- collapse: collapse,
- selectNode: selectNode,
- selectNodeContents: selectNodeContents,
- compareBoundaryPoints: compareBoundaryPoints,
- deleteContents: deleteContents,
- extractContents: extractContents,
- cloneContents: cloneContents,
- insertNode: insertNode,
- surroundContents: surroundContents,
- cloneRange: cloneRange,
- toStringIE: toStringIE
- });
- return self;
- }
- // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
- Range.prototype.toString = function() {
- return this.toStringIE();
- };
- return Range;
- });
- // Included from: js/tinymce/classes/html/Entities.js
- /**
- * Entities.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*jshint bitwise:false */
- /*eslint no-bitwise:0 */
- /**
- * Entity encoder class.
- *
- * @class tinymce.html.Entities
- * @static
- * @version 3.4
- */
- define("tinymce/html/Entities", [
- "tinymce/util/Tools"
- ], function(Tools) {
- var makeMap = Tools.makeMap;
- var namedEntities, baseEntities, reverseEntities,
- attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
- textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
- rawCharsRegExp = /[<>&\"\']/g,
- entityRegExp = /&#([a-z0-9]+);?|&([a-z0-9]+);/gi,
- asciiMap = {
- 128: "\u20AC", 130: "\u201A", 131: "\u0192", 132: "\u201E", 133: "\u2026", 134: "\u2020",
- 135: "\u2021", 136: "\u02C6", 137: "\u2030", 138: "\u0160", 139: "\u2039", 140: "\u0152",
- 142: "\u017D", 145: "\u2018", 146: "\u2019", 147: "\u201C", 148: "\u201D", 149: "\u2022",
- 150: "\u2013", 151: "\u2014", 152: "\u02DC", 153: "\u2122", 154: "\u0161", 155: "\u203A",
- 156: "\u0153", 158: "\u017E", 159: "\u0178"
- };
- // Raw entities
- baseEntities = {
- '\"': '"', // Needs to be escaped since the YUI compressor would otherwise break the code
- "'": ''',
- '<': '<',
- '>': '>',
- '&': '&',
- '\u0060': '`'
- };
- // Reverse lookup table for raw entities
- reverseEntities = {
- '<': '<',
- '>': '>',
- '&': '&',
- '"': '"',
- ''': "'"
- };
- // Decodes text by using the browser
- function nativeDecode(text) {
- var elm;
- elm = document.createElement("div");
- elm.innerHTML = text;
- return elm.textContent || elm.innerText || text;
- }
- // Build a two way lookup table for the entities
- function buildEntitiesLookup(items, radix) {
- var i, chr, entity, lookup = {};
- if (items) {
- items = items.split(',');
- radix = radix || 10;
- // Build entities lookup table
- for (i = 0; i < items.length; i += 2) {
- chr = String.fromCharCode(parseInt(items[i], radix));
- // Only add non base entities
- if (!baseEntities[chr]) {
- entity = '&' + items[i + 1] + ';';
- lookup[chr] = entity;
- lookup[entity] = chr;
- }
- }
- return lookup;
- }
- }
- // Unpack entities lookup where the numbers are in radix 32 to reduce the size
- namedEntities = buildEntitiesLookup(
- '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
- '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
- '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
- '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
- '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
- '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
- '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
- '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
- '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
- '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
- 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
- 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
- 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
- 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
- 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
- '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
- '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
- '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
- '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
- '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
- 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
- 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
- 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
- '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
- '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
- var Entities = {
- /**
- * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded.
- *
- * @method encodeRaw
- * @param {String} text Text to encode.
- * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
- * @return {String} Entity encoded text.
- */
- encodeRaw: function(text, attr) {
- return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
- return baseEntities[chr] || chr;
- });
- },
- /**
- * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
- * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
- * and is exposed as the DOMUtils.encode function.
- *
- * @method encodeAllRaw
- * @param {String} text Text to encode.
- * @return {String} Entity encoded text.
- */
- encodeAllRaw: function(text) {
- return ('' + text).replace(rawCharsRegExp, function(chr) {
- return baseEntities[chr] || chr;
- });
- },
- /**
- * Encodes the specified string using numeric entities. The core entities will be
- * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
- *
- * @method encodeNumeric
- * @param {String} text Text to encode.
- * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
- * @return {String} Entity encoded text.
- */
- encodeNumeric: function(text, attr) {
- return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
- // Multi byte sequence convert it to a single entity
- if (chr.length > 1) {
- return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
- }
- return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
- });
- },
- /**
- * Encodes the specified string using named entities. The core entities will be encoded
- * as named ones but all non lower ascii characters will be encoded into named entities.
- *
- * @method encodeNamed
- * @param {String} text Text to encode.
- * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
- * @param {Object} entities Optional parameter with entities to use.
- * @return {String} Entity encoded text.
- */
- encodeNamed: function(text, attr, entities) {
- entities = entities || namedEntities;
- return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
- return baseEntities[chr] || entities[chr] || chr;
- });
- },
- /**
- * Returns an encode function based on the name(s) and it's optional entities.
- *
- * @method getEncodeFunc
- * @param {String} name Comma separated list of encoders for example named,numeric.
- * @param {String} entities Optional parameter with entities to use instead of the built in set.
- * @return {function} Encode function to be used.
- */
- getEncodeFunc: function(name, entities) {
- entities = buildEntitiesLookup(entities) || namedEntities;
- function encodeNamedAndNumeric(text, attr) {
- return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
- return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
- });
- }
- function encodeCustomNamed(text, attr) {
- return Entities.encodeNamed(text, attr, entities);
- }
- // Replace + with , to be compatible with previous TinyMCE versions
- name = makeMap(name.replace(/\+/g, ','));
- // Named and numeric encoder
- if (name.named && name.numeric) {
- return encodeNamedAndNumeric;
- }
- // Named encoder
- if (name.named) {
- // Custom names
- if (entities) {
- return encodeCustomNamed;
- }
- return Entities.encodeNamed;
- }
- // Numeric
- if (name.numeric) {
- return Entities.encodeNumeric;
- }
- // Raw encoder
- return Entities.encodeRaw;
- },
- /**
- * Decodes the specified string, this will replace entities with raw UTF characters.
- *
- * @method decode
- * @param {String} text Text to entity decode.
- * @return {String} Entity decoded string.
- */
- decode: function(text) {
- return text.replace(entityRegExp, function(all, numeric) {
- if (numeric) {
- if (numeric.charAt(0).toLowerCase() === 'x') {
- numeric = parseInt(numeric.substr(1), 16);
- } else {
- numeric = parseInt(numeric, 10);
- }
- // Support upper UTF
- if (numeric > 0xFFFF) {
- numeric -= 0x10000;
- return String.fromCharCode(0xD800 + (numeric >> 10), 0xDC00 + (numeric & 0x3FF));
- }
- return asciiMap[numeric] || String.fromCharCode(numeric);
- }
- return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
- });
- }
- };
- return Entities;
- });
- // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
- /**
- * StyleSheetLoader.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles loading of external stylesheets and fires events when these are loaded.
- *
- * @class tinymce.dom.StyleSheetLoader
- * @private
- */
- define("tinymce/dom/StyleSheetLoader", [
- "tinymce/util/Tools",
- "tinymce/util/Delay"
- ], function(Tools, Delay) {
- "use strict";
- return function(document, settings) {
- var idCount = 0, loadedStates = {}, maxLoadTime;
- settings = settings || {};
- maxLoadTime = settings.maxLoadTime || 5000;
- function appendToHead(node) {
- document.getElementsByTagName('head')[0].appendChild(node);
- }
- /**
- * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
- *
- * @method load
- * @param {String} url Url to be loaded.
- * @param {Function} loadedCallback Callback to be executed when loaded.
- * @param {Function} errorCallback Callback to be executed when failed loading.
- */
- function load(url, loadedCallback, errorCallback) {
- var link, style, startTime, state;
- function passed() {
- var callbacks = state.passed, i = callbacks.length;
- while (i--) {
- callbacks[i]();
- }
- state.status = 2;
- state.passed = [];
- state.failed = [];
- }
- function failed() {
- var callbacks = state.failed, i = callbacks.length;
- while (i--) {
- callbacks[i]();
- }
- state.status = 3;
- state.passed = [];
- state.failed = [];
- }
- // Sniffs for older WebKit versions that have the link.onload but a broken one
- function isOldWebKit() {
- var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
- return !!(webKitChunks && webKitChunks[1] < 536);
- }
- // Calls the waitCallback until the test returns true or the timeout occurs
- function wait(testCallback, waitCallback) {
- if (!testCallback()) {
- // Wait for timeout
- if ((new Date().getTime()) - startTime < maxLoadTime) {
- Delay.setTimeout(waitCallback);
- } else {
- failed();
- }
- }
- }
- // Workaround for WebKit that doesn't properly support the onload event for link elements
- // Or WebKit that fires the onload event before the StyleSheet is added to the document
- function waitForWebKitLinkLoaded() {
- wait(function() {
- var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
- while (i--) {
- styleSheet = styleSheets[i];
- owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
- if (owner && owner.id === link.id) {
- passed();
- return true;
- }
- }
- }, waitForWebKitLinkLoaded);
- }
- // Workaround for older Geckos that doesn't have any onload event for StyleSheets
- function waitForGeckoLinkLoaded() {
- wait(function() {
- try {
- // Accessing the cssRules will throw an exception until the CSS file is loaded
- var cssRules = style.sheet.cssRules;
- passed();
- return !!cssRules;
- } catch (ex) {
- // Ignore
- }
- }, waitForGeckoLinkLoaded);
- }
- url = Tools._addCacheSuffix(url);
- if (!loadedStates[url]) {
- state = {
- passed: [],
- failed: []
- };
- loadedStates[url] = state;
- } else {
- state = loadedStates[url];
- }
- if (loadedCallback) {
- state.passed.push(loadedCallback);
- }
- if (errorCallback) {
- state.failed.push(errorCallback);
- }
- // Is loading wait for it to pass
- if (state.status == 1) {
- return;
- }
- // Has finished loading and was success
- if (state.status == 2) {
- passed();
- return;
- }
- // Has finished loading and was a failure
- if (state.status == 3) {
- failed();
- return;
- }
- // Start loading
- state.status = 1;
- link = document.createElement('link');
- link.rel = 'stylesheet';
- link.type = 'text/css';
- link.id = 'u' + (idCount++);
- link.async = false;
- link.defer = false;
- startTime = new Date().getTime();
- // Feature detect onload on link element and sniff older webkits since it has an broken onload event
- if ("onload" in link && !isOldWebKit()) {
- link.onload = waitForWebKitLinkLoaded;
- link.onerror = failed;
- } else {
- // Sniff for old Firefox that doesn't support the onload event on link elements
- // TODO: Remove this in the future when everyone uses modern browsers
- if (navigator.userAgent.indexOf("Firefox") > 0) {
- style = document.createElement('style');
- style.textContent = '@import "' + url + '"';
- waitForGeckoLinkLoaded();
- appendToHead(style);
- return;
- }
- // Use the id owner on older webkits
- waitForWebKitLinkLoaded();
- }
- appendToHead(link);
- link.href = url;
- }
- this.load = load;
- };
- });
- // Included from: js/tinymce/classes/dom/DOMUtils.js
- /**
- * DOMUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility class for various DOM manipulation and retrieval functions.
- *
- * @class tinymce.dom.DOMUtils
- * @example
- * // Add a class to an element by id in the page
- * tinymce.DOM.addClass('someid', 'someclass');
- *
- * // Add a class to an element by id inside the editor
- * tinymce.activeEditor.dom.addClass('someid', 'someclass');
- */
- define("tinymce/dom/DOMUtils", [
- "tinymce/dom/Sizzle",
- "tinymce/dom/DomQuery",
- "tinymce/html/Styles",
- "tinymce/dom/EventUtils",
- "tinymce/dom/TreeWalker",
- "tinymce/dom/Range",
- "tinymce/html/Entities",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/dom/StyleSheetLoader"
- ], function(Sizzle, $, Styles, EventUtils, TreeWalker, Range, Entities, Env, Tools, StyleSheetLoader) {
- // Shorten names
- var each = Tools.each, is = Tools.is, grep = Tools.grep, trim = Tools.trim;
- var isIE = Env.ie;
- var simpleSelectorRe = /^([a-z0-9],?)+$/i;
- var whiteSpaceRegExp = /^[ \t\r\n]*$/;
- function setupAttrHooks(domUtils, settings) {
- var attrHooks = {}, keepValues = settings.keep_values, keepUrlHook;
- keepUrlHook = {
- set: function($elm, value, name) {
- if (settings.url_converter) {
- value = settings.url_converter.call(settings.url_converter_scope || domUtils, value, name, $elm[0]);
- }
- $elm.attr('data-mce-' + name, value).attr(name, value);
- },
- get: function($elm, name) {
- return $elm.attr('data-mce-' + name) || $elm.attr(name);
- }
- };
- attrHooks = {
- style: {
- set: function($elm, value) {
- if (value !== null && typeof value === 'object') {
- $elm.css(value);
- return;
- }
- if (keepValues) {
- $elm.attr('data-mce-style', value);
- }
- $elm.attr('style', value);
- },
- get: function($elm) {
- var value = $elm.attr('data-mce-style') || $elm.attr('style');
- value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
- return value;
- }
- }
- };
- if (keepValues) {
- attrHooks.href = attrHooks.src = keepUrlHook;
- }
- return attrHooks;
- }
- function updateInternalStyleAttr(domUtils, $elm) {
- var value = $elm.attr('style');
- value = domUtils.serializeStyle(domUtils.parseStyle(value), $elm[0].nodeName);
- if (!value) {
- value = null;
- }
- $elm.attr('data-mce-style', value);
- }
- function nodeIndex(node, normalized) {
- var idx = 0, lastNodeType, nodeType;
- if (node) {
- for (lastNodeType = node.nodeType, node = node.previousSibling; node; node = node.previousSibling) {
- nodeType = node.nodeType;
- // Normalize text nodes
- if (normalized && nodeType == 3) {
- if (nodeType == lastNodeType || !node.nodeValue.length) {
- continue;
- }
- }
- idx++;
- lastNodeType = nodeType;
- }
- }
- return idx;
- }
- /**
- * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
- *
- * @constructor
- * @method DOMUtils
- * @param {Document} doc Document reference to bind the utility class to.
- * @param {settings} settings Optional settings collection.
- */
- function DOMUtils(doc, settings) {
- var self = this, blockElementsMap;
- self.doc = doc;
- self.win = window;
- self.files = {};
- self.counter = 0;
- self.stdMode = !isIE || doc.documentMode >= 8;
- self.boxModel = !isIE || doc.compatMode == "CSS1Compat" || self.stdMode;
- self.styleSheetLoader = new StyleSheetLoader(doc);
- self.boundEvents = [];
- self.settings = settings = settings || {};
- self.schema = settings.schema;
- self.styles = new Styles({
- url_converter: settings.url_converter,
- url_converter_scope: settings.url_converter_scope
- }, settings.schema);
- self.fixDoc(doc);
- self.events = settings.ownEvents ? new EventUtils(settings.proxy) : EventUtils.Event;
- self.attrHooks = setupAttrHooks(self, settings);
- blockElementsMap = settings.schema ? settings.schema.getBlockElements() : {};
- self.$ = $.overrideDefaults(function() {
- return {
- context: doc,
- element: self.getRoot()
- };
- });
- /**
- * Returns true/false if the specified element is a block element or not.
- *
- * @method isBlock
- * @param {Node/String} node Element/Node to check.
- * @return {Boolean} True/False state if the node is a block element or not.
- */
- self.isBlock = function(node) {
- // Fix for #5446
- if (!node) {
- return false;
- }
- // This function is called in module pattern style since it might be executed with the wrong this scope
- var type = node.nodeType;
- // If it's a node then check the type and use the nodeName
- if (type) {
- return !!(type === 1 && blockElementsMap[node.nodeName]);
- }
- return !!blockElementsMap[node];
- };
- }
- DOMUtils.prototype = {
- $$: function(elm) {
- if (typeof elm == 'string') {
- elm = this.get(elm);
- }
- return this.$(elm);
- },
- root: null,
- fixDoc: function(doc) {
- var settings = this.settings, name;
- if (isIE && settings.schema) {
- // Add missing HTML 4/5 elements to IE
- ('abbr article aside audio canvas ' +
- 'details figcaption figure footer ' +
- 'header hgroup mark menu meter nav ' +
- 'output progress section summary ' +
- 'time video').replace(/\w+/g, function(name) {
- doc.createElement(name);
- });
- // Create all custom elements
- for (name in settings.schema.getCustomElements()) {
- doc.createElement(name);
- }
- }
- },
- clone: function(node, deep) {
- var self = this, clone, doc;
- // TODO: Add feature detection here in the future
- if (!isIE || node.nodeType !== 1 || deep) {
- return node.cloneNode(deep);
- }
- doc = self.doc;
- // Make a HTML5 safe shallow copy
- if (!deep) {
- clone = doc.createElement(node.nodeName);
- // Copy attribs
- each(self.getAttribs(node), function(attr) {
- self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
- });
- return clone;
- }
- return clone.firstChild;
- },
- /**
- * Returns the root node of the document. This is normally the body but might be a DIV. Parents like getParent will not
- * go above the point of this root node.
- *
- * @method getRoot
- * @return {Element} Root element for the utility class.
- */
- getRoot: function() {
- var self = this;
- return self.settings.root_element || self.doc.body;
- },
- /**
- * Returns the viewport of the window.
- *
- * @method getViewPort
- * @param {Window} win Optional window to get viewport of.
- * @return {Object} Viewport object with fields x, y, w and h.
- */
- getViewPort: function(win) {
- var doc, rootElm;
- win = !win ? this.win : win;
- doc = win.document;
- rootElm = this.boxModel ? doc.documentElement : doc.body;
- // Returns viewport size excluding scrollbars
- return {
- x: win.pageXOffset || rootElm.scrollLeft,
- y: win.pageYOffset || rootElm.scrollTop,
- w: win.innerWidth || rootElm.clientWidth,
- h: win.innerHeight || rootElm.clientHeight
- };
- },
- /**
- * Returns the rectangle for a specific element.
- *
- * @method getRect
- * @param {Element/String} elm Element object or element ID to get rectangle from.
- * @return {object} Rectangle for specified element object with x, y, w, h fields.
- */
- getRect: function(elm) {
- var self = this, pos, size;
- elm = self.get(elm);
- pos = self.getPos(elm);
- size = self.getSize(elm);
- return {
- x: pos.x, y: pos.y,
- w: size.w, h: size.h
- };
- },
- /**
- * Returns the size dimensions of the specified element.
- *
- * @method getSize
- * @param {Element/String} elm Element object or element ID to get rectangle from.
- * @return {object} Rectangle for specified element object with w, h fields.
- */
- getSize: function(elm) {
- var self = this, w, h;
- elm = self.get(elm);
- w = self.getStyle(elm, 'width');
- h = self.getStyle(elm, 'height');
- // Non pixel value, then force offset/clientWidth
- if (w.indexOf('px') === -1) {
- w = 0;
- }
- // Non pixel value, then force offset/clientWidth
- if (h.indexOf('px') === -1) {
- h = 0;
- }
- return {
- w: parseInt(w, 10) || elm.offsetWidth || elm.clientWidth,
- h: parseInt(h, 10) || elm.offsetHeight || elm.clientHeight
- };
- },
- /**
- * Returns a node by the specified selector function. This function will
- * loop through all parent nodes and call the specified function for each node.
- * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
- * and the node it found will be returned.
- *
- * @method getParent
- * @param {Node/String} node DOM node to search parents on or ID string.
- * @param {function} selector Selection function or CSS selector to execute on each node.
- * @param {Node} root Optional root element, never go below this point.
- * @return {Node} DOM Node or null if it wasn't found.
- */
- getParent: function(node, selector, root) {
- return this.getParents(node, selector, root, false);
- },
- /**
- * Returns a node list of all parents matching the specified selector function or pattern.
- * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
- *
- * @method getParents
- * @param {Node/String} node DOM node to search parents on or ID string.
- * @param {function} selector Selection function to execute on each node or CSS pattern.
- * @param {Node} root Optional root element, never go below this point.
- * @return {Array} Array of nodes or null if it wasn't found.
- */
- getParents: function(node, selector, root, collect) {
- var self = this, selectorVal, result = [];
- node = self.get(node);
- collect = collect === undefined;
- // Default root on inline mode
- root = root || (self.getRoot().nodeName != 'BODY' ? self.getRoot().parentNode : null);
- // Wrap node name as func
- if (is(selector, 'string')) {
- selectorVal = selector;
- if (selector === '*') {
- selector = function(node) {
- return node.nodeType == 1;
- };
- } else {
- selector = function(node) {
- return self.is(node, selectorVal);
- };
- }
- }
- while (node) {
- if (node == root || !node.nodeType || node.nodeType === 9) {
- break;
- }
- if (!selector || selector(node)) {
- if (collect) {
- result.push(node);
- } else {
- return node;
- }
- }
- node = node.parentNode;
- }
- return collect ? result : null;
- },
- /**
- * Returns the specified element by ID or the input element if it isn't a string.
- *
- * @method get
- * @param {String/Element} n Element id to look for or element to just pass though.
- * @return {Element} Element matching the specified id or null if it wasn't found.
- */
- get: function(elm) {
- var name;
- if (elm && this.doc && typeof elm == 'string') {
- name = elm;
- elm = this.doc.getElementById(elm);
- // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
- if (elm && elm.id !== name) {
- return this.doc.getElementsByName(name)[1];
- }
- }
- return elm;
- },
- /**
- * Returns the next node that matches selector or function
- *
- * @method getNext
- * @param {Node} node Node to find siblings from.
- * @param {String/function} selector Selector CSS expression or function.
- * @return {Node} Next node item matching the selector or null if it wasn't found.
- */
- getNext: function(node, selector) {
- return this._findSib(node, selector, 'nextSibling');
- },
- /**
- * Returns the previous node that matches selector or function
- *
- * @method getPrev
- * @param {Node} node Node to find siblings from.
- * @param {String/function} selector Selector CSS expression or function.
- * @return {Node} Previous node item matching the selector or null if it wasn't found.
- */
- getPrev: function(node, selector) {
- return this._findSib(node, selector, 'previousSibling');
- },
- // #ifndef jquery
- /**
- * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
- * This function is optimized for the most common patterns needed in TinyMCE but it also performs well enough
- * on more complex patterns.
- *
- * @method select
- * @param {String} selector CSS level 3 pattern to select/find elements by.
- * @param {Object} scope Optional root element/scope element to search in.
- * @return {Array} Array with all matched elements.
- * @example
- * // Adds a class to all paragraphs in the currently active editor
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
- *
- * // Adds a class to all spans that have the test class in the currently active editor
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('span.test'), 'someclass')
- */
- select: function(selector, scope) {
- var self = this;
- /*eslint new-cap:0 */
- return Sizzle(selector, self.get(scope) || self.settings.root_element || self.doc, []);
- },
- /**
- * Returns true/false if the specified element matches the specified css pattern.
- *
- * @method is
- * @param {Node/NodeList} elm DOM node to match or an array of nodes to match.
- * @param {String} selector CSS pattern to match the element against.
- */
- is: function(elm, selector) {
- var i;
- // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
- if (elm.length === undefined) {
- // Simple all selector
- if (selector === '*') {
- return elm.nodeType == 1;
- }
- // Simple selector just elements
- if (simpleSelectorRe.test(selector)) {
- selector = selector.toLowerCase().split(/,/);
- elm = elm.nodeName.toLowerCase();
- for (i = selector.length - 1; i >= 0; i--) {
- if (selector[i] == elm) {
- return true;
- }
- }
- return false;
- }
- }
- // Is non element
- if (elm.nodeType && elm.nodeType != 1) {
- return false;
- }
- var elms = elm.nodeType ? [elm] : elm;
- /*eslint new-cap:0 */
- return Sizzle(selector, elms[0].ownerDocument || elms[0], null, elms).length > 0;
- },
- // #endif
- /**
- * Adds the specified element to another element or elements.
- *
- * @method add
- * @param {String/Element/Array} parentElm Element id string, DOM node element or array of ids or elements to add to.
- * @param {String/Element} name Name of new element to add or existing element to add.
- * @param {Object} attrs Optional object collection with arguments to add to the new element(s).
- * @param {String} html Optional inner HTML contents to add for each element.
- * @param {Boolean} create Optional flag if the element should be created or added.
- * @return {Element/Array} Element that got created, or an array of created elements if multiple input elements
- * were passed in.
- * @example
- * // Adds a new paragraph to the end of the active editor
- * tinymce.activeEditor.dom.add(tinymce.activeEditor.getBody(), 'p', {title: 'my title'}, 'Some content');
- */
- add: function(parentElm, name, attrs, html, create) {
- var self = this;
- return this.run(parentElm, function(parentElm) {
- var newElm;
- newElm = is(name, 'string') ? self.doc.createElement(name) : name;
- self.setAttribs(newElm, attrs);
- if (html) {
- if (html.nodeType) {
- newElm.appendChild(html);
- } else {
- self.setHTML(newElm, html);
- }
- }
- return !create ? parentElm.appendChild(newElm) : newElm;
- });
- },
- /**
- * Creates a new element.
- *
- * @method create
- * @param {String} name Name of new element.
- * @param {Object} attrs Optional object name/value collection with element attributes.
- * @param {String} html Optional HTML string to set as inner HTML of the element.
- * @return {Element} HTML DOM node element that got created.
- * @example
- * // Adds an element where the caret/selection is in the active editor
- * var el = tinymce.activeEditor.dom.create('div', {id: 'test', 'class': 'myclass'}, 'some content');
- * tinymce.activeEditor.selection.setNode(el);
- */
- create: function(name, attrs, html) {
- return this.add(this.doc.createElement(name), name, attrs, html, 1);
- },
- /**
- * Creates HTML string for element. The element will be closed unless an empty inner HTML string is passed in.
- *
- * @method createHTML
- * @param {String} name Name of new element.
- * @param {Object} attrs Optional object name/value collection with element attributes.
- * @param {String} html Optional HTML string to set as inner HTML of the element.
- * @return {String} String with new HTML element, for example: <a href="#">test</a>.
- * @example
- * // Creates a html chunk and inserts it at the current selection/caret location
- * tinymce.activeEditor.selection.setContent(tinymce.activeEditor.dom.createHTML('a', {href: 'test.html'}, 'some line'));
- */
- createHTML: function(name, attrs, html) {
- var outHtml = '', key;
- outHtml += '<' + name;
- for (key in attrs) {
- if (attrs.hasOwnProperty(key) && attrs[key] !== null && typeof attrs[key] != 'undefined') {
- outHtml += ' ' + key + '="' + this.encode(attrs[key]) + '"';
- }
- }
- // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
- if (typeof html != "undefined") {
- return outHtml + '>' + html + '</' + name + '>';
- }
- return outHtml + ' />';
- },
- /**
- * Creates a document fragment out of the specified HTML string.
- *
- * @method createFragment
- * @param {String} html Html string to create fragment from.
- * @return {DocumentFragment} Document fragment node.
- */
- createFragment: function(html) {
- var frag, node, doc = this.doc, container;
- container = doc.createElement("div");
- frag = doc.createDocumentFragment();
- if (html) {
- container.innerHTML = html;
- }
- while ((node = container.firstChild)) {
- frag.appendChild(node);
- }
- return frag;
- },
- /**
- * Removes/deletes the specified element(s) from the DOM.
- *
- * @method remove
- * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
- * @param {Boolean} keepChildren Optional state to keep children or not. If set to true all children will be
- * placed at the location of the removed element.
- * @return {Element/Array} HTML DOM element that got removed, or an array of removed elements if multiple input elements
- * were passed in.
- * @example
- * // Removes all paragraphs in the active editor
- * tinymce.activeEditor.dom.remove(tinymce.activeEditor.dom.select('p'));
- *
- * // Removes an element by id in the document
- * tinymce.DOM.remove('mydiv');
- */
- remove: function(node, keepChildren) {
- node = this.$$(node);
- if (keepChildren) {
- node.each(function() {
- var child;
- while ((child = this.firstChild)) {
- if (child.nodeType == 3 && child.data.length === 0) {
- this.removeChild(child);
- } else {
- this.parentNode.insertBefore(child, this);
- }
- }
- }).remove();
- } else {
- node.remove();
- }
- return node.length > 1 ? node.toArray() : node[0];
- },
- /**
- * Sets the CSS style value on a HTML element. The name can be a camelcase string
- * or the CSS style name like background-color.
- *
- * @method setStyle
- * @param {String/Element/Array} elm HTML element/Array of elements to set CSS style value on.
- * @param {String} name Name of the style value to set.
- * @param {String} value Value to set on the style.
- * @example
- * // Sets a style value on all paragraphs in the currently active editor
- * tinymce.activeEditor.dom.setStyle(tinymce.activeEditor.dom.select('p'), 'background-color', 'red');
- *
- * // Sets a style value to an element by id in the current document
- * tinymce.DOM.setStyle('mydiv', 'background-color', 'red');
- */
- setStyle: function(elm, name, value) {
- elm = this.$$(elm).css(name, value);
- if (this.settings.update_styles) {
- updateInternalStyleAttr(this, elm);
- }
- },
- /**
- * Returns the current style or runtime/computed value of an element.
- *
- * @method getStyle
- * @param {String/Element} elm HTML element or element id string to get style from.
- * @param {String} name Style name to return.
- * @param {Boolean} computed Computed style.
- * @return {String} Current style or computed style value of an element.
- */
- getStyle: function(elm, name, computed) {
- elm = this.$$(elm);
- if (computed) {
- return elm.css(name);
- }
- // Camelcase it, if needed
- name = name.replace(/-(\D)/g, function(a, b) {
- return b.toUpperCase();
- });
- if (name == 'float') {
- name = Env.ie && Env.ie < 12 ? 'styleFloat' : 'cssFloat';
- }
- return elm[0] && elm[0].style ? elm[0].style[name] : undefined;
- },
- /**
- * Sets multiple styles on the specified element(s).
- *
- * @method setStyles
- * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set styles on.
- * @param {Object} styles Name/Value collection of style items to add to the element(s).
- * @example
- * // Sets styles on all paragraphs in the currently active editor
- * tinymce.activeEditor.dom.setStyles(tinymce.activeEditor.dom.select('p'), {'background-color': 'red', 'color': 'green'});
- *
- * // Sets styles to an element by id in the current document
- * tinymce.DOM.setStyles('mydiv', {'background-color': 'red', 'color': 'green'});
- */
- setStyles: function(elm, styles) {
- elm = this.$$(elm).css(styles);
- if (this.settings.update_styles) {
- updateInternalStyleAttr(this, elm);
- }
- },
- /**
- * Removes all attributes from an element or elements.
- *
- * @method removeAllAttribs
- * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
- */
- removeAllAttribs: function(e) {
- return this.run(e, function(e) {
- var i, attrs = e.attributes;
- for (i = attrs.length - 1; i >= 0; i--) {
- e.removeAttributeNode(attrs.item(i));
- }
- });
- },
- /**
- * Sets the specified attribute of an element or elements.
- *
- * @method setAttrib
- * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attribute on.
- * @param {String} name Name of attribute to set.
- * @param {String} value Value to set on the attribute - if this value is falsy like null, 0 or '' it will remove
- * the attribute instead.
- * @example
- * // Sets class attribute on all paragraphs in the active editor
- * tinymce.activeEditor.dom.setAttrib(tinymce.activeEditor.dom.select('p'), 'class', 'myclass');
- *
- * // Sets class attribute on a specific element in the current page
- * tinymce.dom.setAttrib('mydiv', 'class', 'myclass');
- */
- setAttrib: function(elm, name, value) {
- var self = this, originalValue, hook, settings = self.settings;
- if (value === '') {
- value = null;
- }
- elm = self.$$(elm);
- originalValue = elm.attr(name);
- if (!elm.length) {
- return;
- }
- hook = self.attrHooks[name];
- if (hook && hook.set) {
- hook.set(elm, value, name);
- } else {
- elm.attr(name, value);
- }
- if (originalValue != value && settings.onSetAttrib) {
- settings.onSetAttrib({
- attrElm: elm,
- attrName: name,
- attrValue: value
- });
- }
- },
- /**
- * Sets two or more specified attributes of an element or elements.
- *
- * @method setAttribs
- * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set attributes on.
- * @param {Object} attrs Name/Value collection of attribute items to add to the element(s).
- * @example
- * // Sets class and title attributes on all paragraphs in the active editor
- * tinymce.activeEditor.dom.setAttribs(tinymce.activeEditor.dom.select('p'), {'class': 'myclass', title: 'some title'});
- *
- * // Sets class and title attributes on a specific element in the current page
- * tinymce.DOM.setAttribs('mydiv', {'class': 'myclass', title: 'some title'});
- */
- setAttribs: function(elm, attrs) {
- var self = this;
- self.$$(elm).each(function(i, node) {
- each(attrs, function(value, name) {
- self.setAttrib(node, name, value);
- });
- });
- },
- /**
- * Returns the specified attribute by name.
- *
- * @method getAttrib
- * @param {String/Element} elm Element string id or DOM element to get attribute from.
- * @param {String} name Name of attribute to get.
- * @param {String} defaultVal Optional default value to return if the attribute didn't exist.
- * @return {String} Attribute value string, default value or null if the attribute wasn't found.
- */
- getAttrib: function(elm, name, defaultVal) {
- var self = this, hook, value;
- elm = self.$$(elm);
- if (elm.length) {
- hook = self.attrHooks[name];
- if (hook && hook.get) {
- value = hook.get(elm, name);
- } else {
- value = elm.attr(name);
- }
- }
- if (typeof value == 'undefined') {
- value = defaultVal || '';
- }
- return value;
- },
- /**
- * Returns the absolute x, y position of a node. The position will be returned in an object with x, y fields.
- *
- * @method getPos
- * @param {Element/String} elm HTML element or element id to get x, y position from.
- * @param {Element} rootElm Optional root element to stop calculations at.
- * @return {object} Absolute position of the specified element object with x, y fields.
- */
- getPos: function(elm, rootElm) {
- var self = this, x = 0, y = 0, offsetParent, doc = self.doc, body = doc.body, pos;
- elm = self.get(elm);
- rootElm = rootElm || body;
- if (elm) {
- // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
- // Fallback to offsetParent calculations if the body isn't static better since it stops at the body root
- if (rootElm === body && elm.getBoundingClientRect && $(body).css('position') === 'static') {
- pos = elm.getBoundingClientRect();
- rootElm = self.boxModel ? doc.documentElement : body;
- // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
- // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
- x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - rootElm.clientLeft;
- y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - rootElm.clientTop;
- return {x: x, y: y};
- }
- offsetParent = elm;
- while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
- x += offsetParent.offsetLeft || 0;
- y += offsetParent.offsetTop || 0;
- offsetParent = offsetParent.offsetParent;
- }
- offsetParent = elm.parentNode;
- while (offsetParent && offsetParent != rootElm && offsetParent.nodeType) {
- x -= offsetParent.scrollLeft || 0;
- y -= offsetParent.scrollTop || 0;
- offsetParent = offsetParent.parentNode;
- }
- }
- return {x: x, y: y};
- },
- /**
- * Parses the specified style value into an object collection. This parser will also
- * merge and remove any redundant items that browsers might have added. It will also convert non-hex
- * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
- *
- * @method parseStyle
- * @param {String} cssText Style value to parse, for example: border:1px solid red;.
- * @return {Object} Object representation of that style, for example: {border: '1px solid red'}
- */
- parseStyle: function(cssText) {
- return this.styles.parse(cssText);
- },
- /**
- * Serializes the specified style object into a string.
- *
- * @method serializeStyle
- * @param {Object} styles Object to serialize as string, for example: {border: '1px solid red'}
- * @param {String} name Optional element name.
- * @return {String} String representation of the style object, for example: border: 1px solid red.
- */
- serializeStyle: function(styles, name) {
- return this.styles.serialize(styles, name);
- },
- /**
- * Adds a style element at the top of the document with the specified cssText content.
- *
- * @method addStyle
- * @param {String} cssText CSS Text style to add to top of head of document.
- */
- addStyle: function(cssText) {
- var self = this, doc = self.doc, head, styleElm;
- // Prevent inline from loading the same styles twice
- if (self !== DOMUtils.DOM && doc === document) {
- var addedStyles = DOMUtils.DOM.addedStyles;
- addedStyles = addedStyles || [];
- if (addedStyles[cssText]) {
- return;
- }
- addedStyles[cssText] = true;
- DOMUtils.DOM.addedStyles = addedStyles;
- }
- // Create style element if needed
- styleElm = doc.getElementById('mceDefaultStyles');
- if (!styleElm) {
- styleElm = doc.createElement('style');
- styleElm.id = 'mceDefaultStyles';
- styleElm.type = 'text/css';
- head = doc.getElementsByTagName('head')[0];
- if (head.firstChild) {
- head.insertBefore(styleElm, head.firstChild);
- } else {
- head.appendChild(styleElm);
- }
- }
- // Append style data to old or new style element
- if (styleElm.styleSheet) {
- styleElm.styleSheet.cssText += cssText;
- } else {
- styleElm.appendChild(doc.createTextNode(cssText));
- }
- },
- /**
- * Imports/loads the specified CSS file into the document bound to the class.
- *
- * @method loadCSS
- * @param {String} url URL to CSS file to load.
- * @example
- * // Loads a CSS file dynamically into the current document
- * tinymce.DOM.loadCSS('somepath/some.css');
- *
- * // Loads a CSS file into the currently active editor instance
- * tinymce.activeEditor.dom.loadCSS('somepath/some.css');
- *
- * // Loads a CSS file into an editor instance by id
- * tinymce.get('someid').dom.loadCSS('somepath/some.css');
- *
- * // Loads multiple CSS files into the current document
- * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
- */
- loadCSS: function(url) {
- var self = this, doc = self.doc, head;
- // Prevent inline from loading the same CSS file twice
- if (self !== DOMUtils.DOM && doc === document) {
- DOMUtils.DOM.loadCSS(url);
- return;
- }
- if (!url) {
- url = '';
- }
- head = doc.getElementsByTagName('head')[0];
- each(url.split(','), function(url) {
- var link;
- url = Tools._addCacheSuffix(url);
- if (self.files[url]) {
- return;
- }
- self.files[url] = true;
- link = self.create('link', {rel: 'stylesheet', href: url});
- // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
- // This fix seems to resolve that issue by recalcing the document once a stylesheet finishes loading
- // It's ugly but it seems to work fine.
- if (isIE && doc.documentMode && doc.recalc) {
- link.onload = function() {
- if (doc.recalc) {
- doc.recalc();
- }
- link.onload = null;
- };
- }
- head.appendChild(link);
- });
- },
- /**
- * Adds a class to the specified element or elements.
- *
- * @method addClass
- * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
- * @param {String} cls Class name to add to each element.
- * @return {String/Array} String with new class value or array with new class values for all elements.
- * @example
- * // Adds a class to all paragraphs in the active editor
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'myclass');
- *
- * // Adds a class to a specific element in the current page
- * tinymce.DOM.addClass('mydiv', 'myclass');
- */
- addClass: function(elm, cls) {
- this.$$(elm).addClass(cls);
- },
- /**
- * Removes a class from the specified element or elements.
- *
- * @method removeClass
- * @param {String/Element/Array} elm Element ID string or DOM element or array with elements or IDs.
- * @param {String} cls Class name to remove from each element.
- * @return {String/Array} String of remaining class name(s), or an array of strings if multiple input elements
- * were passed in.
- * @example
- * // Removes a class from all paragraphs in the active editor
- * tinymce.activeEditor.dom.removeClass(tinymce.activeEditor.dom.select('p'), 'myclass');
- *
- * // Removes a class from a specific element in the current page
- * tinymce.DOM.removeClass('mydiv', 'myclass');
- */
- removeClass: function(elm, cls) {
- this.toggleClass(elm, cls, false);
- },
- /**
- * Returns true if the specified element has the specified class.
- *
- * @method hasClass
- * @param {String/Element} elm HTML element or element id string to check CSS class on.
- * @param {String} cls CSS class to check for.
- * @return {Boolean} true/false if the specified element has the specified class.
- */
- hasClass: function(elm, cls) {
- return this.$$(elm).hasClass(cls);
- },
- /**
- * Toggles the specified class on/off.
- *
- * @method toggleClass
- * @param {Element} elm Element to toggle class on.
- * @param {[type]} cls Class to toggle on/off.
- * @param {[type]} state Optional state to set.
- */
- toggleClass: function(elm, cls, state) {
- this.$$(elm).toggleClass(cls, state).each(function() {
- if (this.className === '') {
- $(this).attr('class', null);
- }
- });
- },
- /**
- * Shows the specified element(s) by ID by setting the "display" style.
- *
- * @method show
- * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to show.
- */
- show: function(elm) {
- this.$$(elm).show();
- },
- /**
- * Hides the specified element(s) by ID by setting the "display" style.
- *
- * @method hide
- * @param {String/Element/Array} elm ID of DOM element or DOM element or array with elements or IDs to hide.
- * @example
- * // Hides an element by id in the document
- * tinymce.DOM.hide('myid');
- */
- hide: function(elm) {
- this.$$(elm).hide();
- },
- /**
- * Returns true/false if the element is hidden or not by checking the "display" style.
- *
- * @method isHidden
- * @param {String/Element} elm Id or element to check display state on.
- * @return {Boolean} true/false if the element is hidden or not.
- */
- isHidden: function(elm) {
- return this.$$(elm).css('display') == 'none';
- },
- /**
- * Returns a unique id. This can be useful when generating elements on the fly.
- * This method will not check if the element already exists.
- *
- * @method uniqueId
- * @param {String} prefix Optional prefix to add in front of all ids - defaults to "mce_".
- * @return {String} Unique id.
- */
- uniqueId: function(prefix) {
- return (!prefix ? 'mce_' : prefix) + (this.counter++);
- },
- /**
- * Sets the specified HTML content inside the element or elements. The HTML will first be processed. This means
- * URLs will get converted, hex color values fixed etc. Check processHTML for details.
- *
- * @method setHTML
- * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set HTML inside of.
- * @param {String} html HTML content to set as inner HTML of the element.
- * @example
- * // Sets the inner HTML of all paragraphs in the active editor
- * tinymce.activeEditor.dom.setHTML(tinymce.activeEditor.dom.select('p'), 'some inner html');
- *
- * // Sets the inner HTML of an element by id in the document
- * tinymce.DOM.setHTML('mydiv', 'some inner html');
- */
- setHTML: function(elm, html) {
- elm = this.$$(elm);
- if (isIE) {
- elm.each(function(i, target) {
- if (target.canHaveHTML === false) {
- return;
- }
- // Remove all child nodes, IE keeps empty text nodes in DOM
- while (target.firstChild) {
- target.removeChild(target.firstChild);
- }
- try {
- // IE will remove comments from the beginning
- // unless you padd the contents with something
- target.innerHTML = '<br>' + html;
- target.removeChild(target.firstChild);
- } catch (ex) {
- // IE sometimes produces an unknown runtime error on innerHTML if it's a div inside a p
- $('<div>').html('<br>' + html).contents().slice(1).appendTo(target);
- }
- return html;
- });
- } else {
- elm.html(html);
- }
- },
- /**
- * Returns the outer HTML of an element.
- *
- * @method getOuterHTML
- * @param {String/Element} elm Element ID or element object to get outer HTML from.
- * @return {String} Outer HTML string.
- * @example
- * tinymce.DOM.getOuterHTML(editorElement);
- * tinymce.activeEditor.getOuterHTML(tinymce.activeEditor.getBody());
- */
- getOuterHTML: function(elm) {
- elm = this.get(elm);
- // Older FF doesn't have outerHTML 3.6 is still used by some orgaizations
- return elm.nodeType == 1 && "outerHTML" in elm ? elm.outerHTML : $('<div>').append($(elm).clone()).html();
- },
- /**
- * Sets the specified outer HTML on an element or elements.
- *
- * @method setOuterHTML
- * @param {Element/String/Array} elm DOM element, element id string or array of elements/ids to set outer HTML on.
- * @param {Object} html HTML code to set as outer value for the element.
- * @example
- * // Sets the outer HTML of all paragraphs in the active editor
- * tinymce.activeEditor.dom.setOuterHTML(tinymce.activeEditor.dom.select('p'), '<div>some html</div>');
- *
- * // Sets the outer HTML of an element by id in the document
- * tinymce.DOM.setOuterHTML('mydiv', '<div>some html</div>');
- */
- setOuterHTML: function(elm, html) {
- var self = this;
- self.$$(elm).each(function() {
- try {
- // Older FF doesn't have outerHTML 3.6 is still used by some organizations
- if ("outerHTML" in this) {
- this.outerHTML = html;
- return;
- }
- } catch (ex) {
- // Ignore
- }
- // OuterHTML for IE it sometimes produces an "unknown runtime error"
- self.remove($(this).html(html), true);
- });
- },
- /**
- * Entity decodes a string. This method decodes any HTML entities, such as å.
- *
- * @method decode
- * @param {String} s String to decode entities on.
- * @return {String} Entity decoded string.
- */
- decode: Entities.decode,
- /**
- * Entity encodes a string. This method encodes the most common entities, such as <>"&.
- *
- * @method encode
- * @param {String} text String to encode with entities.
- * @return {String} Entity encoded string.
- */
- encode: Entities.encodeAllRaw,
- /**
- * Inserts an element after the reference element.
- *
- * @method insertAfter
- * @param {Element} node Element to insert after the reference.
- * @param {Element/String/Array} referenceNode Reference element, element id or array of elements to insert after.
- * @return {Element/Array} Element that got added or an array with elements.
- */
- insertAfter: function(node, referenceNode) {
- referenceNode = this.get(referenceNode);
- return this.run(node, function(node) {
- var parent, nextSibling;
- parent = referenceNode.parentNode;
- nextSibling = referenceNode.nextSibling;
- if (nextSibling) {
- parent.insertBefore(node, nextSibling);
- } else {
- parent.appendChild(node);
- }
- return node;
- });
- },
- /**
- * Replaces the specified element or elements with the new element specified. The new element will
- * be cloned if multiple input elements are passed in.
- *
- * @method replace
- * @param {Element} newElm New element to replace old ones with.
- * @param {Element/String/Array} oldElm Element DOM node, element id or array of elements or ids to replace.
- * @param {Boolean} keepChildren Optional keep children state, if set to true child nodes from the old object will be added
- * to new ones.
- */
- replace: function(newElm, oldElm, keepChildren) {
- var self = this;
- return self.run(oldElm, function(oldElm) {
- if (is(oldElm, 'array')) {
- newElm = newElm.cloneNode(true);
- }
- if (keepChildren) {
- each(grep(oldElm.childNodes), function(node) {
- newElm.appendChild(node);
- });
- }
- return oldElm.parentNode.replaceChild(newElm, oldElm);
- });
- },
- /**
- * Renames the specified element and keeps its attributes and children.
- *
- * @method rename
- * @param {Element} elm Element to rename.
- * @param {String} name Name of the new element.
- * @return {Element} New element or the old element if it needed renaming.
- */
- rename: function(elm, name) {
- var self = this, newElm;
- if (elm.nodeName != name.toUpperCase()) {
- // Rename block element
- newElm = self.create(name);
- // Copy attribs to new block
- each(self.getAttribs(elm), function(attrNode) {
- self.setAttrib(newElm, attrNode.nodeName, self.getAttrib(elm, attrNode.nodeName));
- });
- // Replace block
- self.replace(newElm, elm, 1);
- }
- return newElm || elm;
- },
- /**
- * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
- *
- * @method findCommonAncestor
- * @param {Element} a Element to find common ancestor of.
- * @param {Element} b Element to find common ancestor of.
- * @return {Element} Common ancestor element of the two input elements.
- */
- findCommonAncestor: function(a, b) {
- var ps = a, pe;
- while (ps) {
- pe = b;
- while (pe && ps != pe) {
- pe = pe.parentNode;
- }
- if (ps == pe) {
- break;
- }
- ps = ps.parentNode;
- }
- if (!ps && a.ownerDocument) {
- return a.ownerDocument.documentElement;
- }
- return ps;
- },
- /**
- * Parses the specified RGB color value and returns a hex version of that color.
- *
- * @method toHex
- * @param {String} rgbVal RGB string value like rgb(1,2,3)
- * @return {String} Hex version of that RGB value like #FF00FF.
- */
- toHex: function(rgbVal) {
- return this.styles.toHex(Tools.trim(rgbVal));
- },
- /**
- * Executes the specified function on the element by id or dom element node or array of elements/id.
- *
- * @method run
- * @param {String/Element/Array} elm ID or DOM element object or array with ids or elements.
- * @param {function} func Function to execute for each item.
- * @param {Object} scope Optional scope to execute the function in.
- * @return {Object/Array} Single object, or an array of objects if multiple input elements were passed in.
- */
- run: function(elm, func, scope) {
- var self = this, result;
- if (typeof elm === 'string') {
- elm = self.get(elm);
- }
- if (!elm) {
- return false;
- }
- scope = scope || this;
- if (!elm.nodeType && (elm.length || elm.length === 0)) {
- result = [];
- each(elm, function(elm, i) {
- if (elm) {
- if (typeof elm == 'string') {
- elm = self.get(elm);
- }
- result.push(func.call(scope, elm, i));
- }
- });
- return result;
- }
- return func.call(scope, elm);
- },
- /**
- * Returns a NodeList with attributes for the element.
- *
- * @method getAttribs
- * @param {HTMLElement/string} elm Element node or string id to get attributes from.
- * @return {NodeList} NodeList with attributes.
- */
- getAttribs: function(elm) {
- var attrs;
- elm = this.get(elm);
- if (!elm) {
- return [];
- }
- if (isIE) {
- attrs = [];
- // Object will throw exception in IE
- if (elm.nodeName == 'OBJECT') {
- return elm.attributes;
- }
- // IE doesn't keep the selected attribute if you clone option elements
- if (elm.nodeName === 'OPTION' && this.getAttrib(elm, 'selected')) {
- attrs.push({specified: 1, nodeName: 'selected'});
- }
- // It's crazy that this is faster in IE but it's because it returns all attributes all the time
- var attrRegExp = /<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi;
- elm.cloneNode(false).outerHTML.replace(attrRegExp, '').replace(/[\w:\-]+/gi, function(a) {
- attrs.push({specified: 1, nodeName: a});
- });
- return attrs;
- }
- return elm.attributes;
- },
- /**
- * Returns true/false if the specified node is to be considered empty or not.
- *
- * @example
- * tinymce.DOM.isEmpty(node, {img: true});
- * @method isEmpty
- * @param {Object} elements Optional name/value object with elements that are automatically treated as non-empty elements.
- * @return {Boolean} true/false if the node is empty or not.
- */
- isEmpty: function(node, elements) {
- var self = this, i, attributes, type, walker, name, brCount = 0;
- node = node.firstChild;
- if (node) {
- walker = new TreeWalker(node, node.parentNode);
- elements = elements || (self.schema ? self.schema.getNonEmptyElements() : null);
- do {
- type = node.nodeType;
- if (type === 1) {
- // Ignore bogus elements
- if (node.getAttribute('data-mce-bogus')) {
- continue;
- }
- // Keep empty elements like <img />
- name = node.nodeName.toLowerCase();
- if (elements && elements[name]) {
- // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
- if (name === 'br') {
- brCount++;
- continue;
- }
- return false;
- }
- // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
- attributes = self.getAttribs(node);
- i = attributes.length;
- while (i--) {
- name = attributes[i].nodeName;
- if (name === "name" || name === 'data-mce-bookmark') {
- return false;
- }
- }
- }
- // Keep comment nodes
- if (type == 8) {
- return false;
- }
- // Keep non whitespace text nodes
- if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) {
- return false;
- }
- } while ((node = walker.next()));
- }
- return brCount <= 1;
- },
- /**
- * Creates a new DOM Range object. This will use the native DOM Range API if it's
- * available. If it's not, it will fall back to the custom TinyMCE implementation.
- *
- * @method createRng
- * @return {DOMRange} DOM Range object.
- * @example
- * var rng = tinymce.DOM.createRng();
- * alert(rng.startContainer + "," + rng.startOffset);
- */
- createRng: function() {
- var doc = this.doc;
- return doc.createRange ? doc.createRange() : new Range(this);
- },
- /**
- * Returns the index of the specified node within its parent.
- *
- * @method nodeIndex
- * @param {Node} node Node to look for.
- * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
- * @return {Number} Index of the specified node.
- */
- nodeIndex: nodeIndex,
- /**
- * Splits an element into two new elements and places the specified split
- * element or elements between the new ones. For example splitting the paragraph at the bold element in
- * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>.
- *
- * @method split
- * @param {Element} parentElm Parent element to split.
- * @param {Element} splitElm Element to split at.
- * @param {Element} replacementElm Optional replacement element to replace the split element with.
- * @return {Element} Returns the split element or the replacement element if that is specified.
- */
- split: function(parentElm, splitElm, replacementElm) {
- var self = this, r = self.createRng(), bef, aft, pa;
- // W3C valid browsers tend to leave empty nodes to the left/right side of the contents - this makes sense
- // but we don't want that in our code since it serves no purpose for the end user
- // For example splitting this html at the bold element:
- // <p>text 1<span><b>CHOP</b></span>text 2</p>
- // would produce:
- // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
- // this function will then trim off empty edges and produce:
- // <p>text 1</p><b>CHOP</b><p>text 2</p>
- function trimNode(node) {
- var i, children = node.childNodes, type = node.nodeType;
- function surroundedBySpans(node) {
- var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
- var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
- return previousIsSpan && nextIsSpan;
- }
- if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') {
- return;
- }
- for (i = children.length - 1; i >= 0; i--) {
- trimNode(children[i]);
- }
- if (type != 9) {
- // Keep non whitespace text nodes
- if (type == 3 && node.nodeValue.length > 0) {
- // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
- // Also keep text nodes with only spaces if surrounded by spans.
- // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
- var trimmedLength = trim(node.nodeValue).length;
- if (!self.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node)) {
- return;
- }
- } else if (type == 1) {
- // If the only child is a bookmark then move it up
- children = node.childNodes;
- // TODO fix this complex if
- if (children.length == 1 && children[0] && children[0].nodeType == 1 &&
- children[0].getAttribute('data-mce-type') == 'bookmark') {
- node.parentNode.insertBefore(children[0], node);
- }
- // Keep non empty elements or img, hr etc
- if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) {
- return;
- }
- }
- self.remove(node);
- }
- return node;
- }
- if (parentElm && splitElm) {
- // Get before chunk
- r.setStart(parentElm.parentNode, self.nodeIndex(parentElm));
- r.setEnd(splitElm.parentNode, self.nodeIndex(splitElm));
- bef = r.extractContents();
- // Get after chunk
- r = self.createRng();
- r.setStart(splitElm.parentNode, self.nodeIndex(splitElm) + 1);
- r.setEnd(parentElm.parentNode, self.nodeIndex(parentElm) + 1);
- aft = r.extractContents();
- // Insert before chunk
- pa = parentElm.parentNode;
- pa.insertBefore(trimNode(bef), parentElm);
- // Insert middle chunk
- if (replacementElm) {
- pa.insertBefore(replacementElm, parentElm);
- //pa.replaceChild(replacementElm, splitElm);
- } else {
- pa.insertBefore(splitElm, parentElm);
- }
- // Insert after chunk
- pa.insertBefore(trimNode(aft), parentElm);
- self.remove(parentElm);
- return replacementElm || splitElm;
- }
- },
- /**
- * Adds an event handler to the specified object.
- *
- * @method bind
- * @param {Element/Document/Window/Array} target Target element to bind events to.
- * handler to or an array of elements/ids/documents.
- * @param {String} name Name of event handler to add, for example: click.
- * @param {function} func Function to execute when the event occurs.
- * @param {Object} scope Optional scope to execute the function in.
- * @return {function} Function callback handler the same as the one passed in.
- */
- bind: function(target, name, func, scope) {
- var self = this;
- if (Tools.isArray(target)) {
- var i = target.length;
- while (i--) {
- target[i] = self.bind(target[i], name, func, scope);
- }
- return target;
- }
- // Collect all window/document events bound by editor instance
- if (self.settings.collect && (target === self.doc || target === self.win)) {
- self.boundEvents.push([target, name, func, scope]);
- }
- return self.events.bind(target, name, func, scope || self);
- },
- /**
- * Removes the specified event handler by name and function from an element or collection of elements.
- *
- * @method unbind
- * @param {Element/Document/Window/Array} target Target element to unbind events on.
- * @param {String} name Event handler name, for example: "click"
- * @param {function} func Function to remove.
- * @return {bool/Array} Bool state of true if the handler was removed, or an array of states if multiple input elements
- * were passed in.
- */
- unbind: function(target, name, func) {
- var self = this, i;
- if (Tools.isArray(target)) {
- i = target.length;
- while (i--) {
- target[i] = self.unbind(target[i], name, func);
- }
- return target;
- }
- // Remove any bound events matching the input
- if (self.boundEvents && (target === self.doc || target === self.win)) {
- i = self.boundEvents.length;
- while (i--) {
- var item = self.boundEvents[i];
- if (target == item[0] && (!name || name == item[1]) && (!func || func == item[2])) {
- this.events.unbind(item[0], item[1], item[2]);
- }
- }
- }
- return this.events.unbind(target, name, func);
- },
- /**
- * Fires the specified event name with object on target.
- *
- * @method fire
- * @param {Node/Document/Window} target Target element or object to fire event on.
- * @param {String} name Name of the event to fire.
- * @param {Object} evt Event object to send.
- * @return {Event} Event object.
- */
- fire: function(target, name, evt) {
- return this.events.fire(target, name, evt);
- },
- // Returns the content editable state of a node
- getContentEditable: function(node) {
- var contentEditable;
- // Check type
- if (!node || node.nodeType != 1) {
- return null;
- }
- // Check for fake content editable
- contentEditable = node.getAttribute("data-mce-contenteditable");
- if (contentEditable && contentEditable !== "inherit") {
- return contentEditable;
- }
- // Check for real content editable
- return node.contentEditable !== "inherit" ? node.contentEditable : null;
- },
- getContentEditableParent: function(node) {
- var root = this.getRoot(), state = null;
- for (; node && node !== root; node = node.parentNode) {
- state = this.getContentEditable(node);
- if (state !== null) {
- break;
- }
- }
- return state;
- },
- /**
- * Destroys all internal references to the DOM to solve IE leak issues.
- *
- * @method destroy
- */
- destroy: function() {
- var self = this;
- // Unbind all events bound to window/document by editor instance
- if (self.boundEvents) {
- var i = self.boundEvents.length;
- while (i--) {
- var item = self.boundEvents[i];
- this.events.unbind(item[0], item[1], item[2]);
- }
- self.boundEvents = null;
- }
- // Restore sizzle document to window.document
- // Since the current document might be removed producing "Permission denied" on IE see #6325
- if (Sizzle.setDocument) {
- Sizzle.setDocument();
- }
- self.win = self.doc = self.root = self.events = self.frag = null;
- },
- isChildOf: function(node, parent) {
- while (node) {
- if (parent === node) {
- return true;
- }
- node = node.parentNode;
- }
- return false;
- },
- // #ifdef debug
- dumpRng: function(r) {
- return (
- 'startContainer: ' + r.startContainer.nodeName +
- ', startOffset: ' + r.startOffset +
- ', endContainer: ' + r.endContainer.nodeName +
- ', endOffset: ' + r.endOffset
- );
- },
- // #endif
- _findSib: function(node, selector, name) {
- var self = this, func = selector;
- if (node) {
- // If expression make a function of it using is
- if (typeof func == 'string') {
- func = function(node) {
- return self.is(node, selector);
- };
- }
- // Loop all siblings
- for (node = node[name]; node; node = node[name]) {
- if (func(node)) {
- return node;
- }
- }
- }
- return null;
- }
- };
- /**
- * Instance of DOMUtils for the current document.
- *
- * @static
- * @property DOM
- * @type tinymce.dom.DOMUtils
- * @example
- * // Example of how to add a class to some element by id
- * tinymce.DOM.addClass('someid', 'someclass');
- */
- DOMUtils.DOM = new DOMUtils(document);
- DOMUtils.nodeIndex = nodeIndex;
- return DOMUtils;
- });
- // Included from: js/tinymce/classes/dom/ScriptLoader.js
- /**
- * ScriptLoader.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*globals console*/
- /**
- * This class handles asynchronous/synchronous loading of JavaScript files it will execute callbacks
- * when various items gets loaded. This class is useful to load external JavaScript files.
- *
- * @class tinymce.dom.ScriptLoader
- * @example
- * // Load a script from a specific URL using the global script loader
- * tinymce.ScriptLoader.load('somescript.js');
- *
- * // Load a script using a unique instance of the script loader
- * var scriptLoader = new tinymce.dom.ScriptLoader();
- *
- * scriptLoader.load('somescript.js');
- *
- * // Load multiple scripts
- * var scriptLoader = new tinymce.dom.ScriptLoader();
- *
- * scriptLoader.add('somescript1.js');
- * scriptLoader.add('somescript2.js');
- * scriptLoader.add('somescript3.js');
- *
- * scriptLoader.loadQueue(function() {
- * alert('All scripts are now loaded.');
- * });
- */
- define("tinymce/dom/ScriptLoader", [
- "tinymce/dom/DOMUtils",
- "tinymce/util/Tools"
- ], function(DOMUtils, Tools) {
- var DOM = DOMUtils.DOM;
- var each = Tools.each, grep = Tools.grep;
- function ScriptLoader() {
- var QUEUED = 0,
- LOADING = 1,
- LOADED = 2,
- states = {},
- queue = [],
- scriptLoadedCallbacks = {},
- queueLoadedCallbacks = [],
- loading = 0,
- undef;
- /**
- * Loads a specific script directly without adding it to the load queue.
- *
- * @method load
- * @param {String} url Absolute URL to script to add.
- * @param {function} callback Optional callback function to execute ones this script gets loaded.
- */
- function loadScript(url, callback) {
- var dom = DOM, elm, id;
- // Execute callback when script is loaded
- function done() {
- dom.remove(id);
- if (elm) {
- elm.onreadystatechange = elm.onload = elm = null;
- }
- callback();
- }
- function error() {
- /*eslint no-console:0 */
- // Report the error so it's easier for people to spot loading errors
- if (typeof console !== "undefined" && console.log) {
- console.log("Failed to load: " + url);
- }
- // We can't mark it as done if there is a load error since
- // A) We don't want to produce 404 errors on the server and
- // B) the onerror event won't fire on all browsers.
- // done();
- }
- id = dom.uniqueId();
- // Create new script element
- elm = document.createElement('script');
- elm.id = id;
- elm.type = 'text/javascript';
- elm.src = Tools._addCacheSuffix(url);
- // Seems that onreadystatechange works better on IE 10 onload seems to fire incorrectly
- if ("onreadystatechange" in elm) {
- elm.onreadystatechange = function() {
- if (/loaded|complete/.test(elm.readyState)) {
- done();
- }
- };
- } else {
- elm.onload = done;
- }
- // Add onerror event will get fired on some browsers but not all of them
- elm.onerror = error;
- // Add script to document
- (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
- }
- /**
- * Returns true/false if a script has been loaded or not.
- *
- * @method isDone
- * @param {String} url URL to check for.
- * @return {Boolean} true/false if the URL is loaded.
- */
- this.isDone = function(url) {
- return states[url] == LOADED;
- };
- /**
- * Marks a specific script to be loaded. This can be useful if a script got loaded outside
- * the script loader or to skip it from loading some script.
- *
- * @method markDone
- * @param {string} url Absolute URL to the script to mark as loaded.
- */
- this.markDone = function(url) {
- states[url] = LOADED;
- };
- /**
- * Adds a specific script to the load queue of the script loader.
- *
- * @method add
- * @param {String} url Absolute URL to script to add.
- * @param {function} callback Optional callback function to execute ones this script gets loaded.
- * @param {Object} scope Optional scope to execute callback in.
- */
- this.add = this.load = function(url, callback, scope) {
- var state = states[url];
- // Add url to load queue
- if (state == undef) {
- queue.push(url);
- states[url] = QUEUED;
- }
- if (callback) {
- // Store away callback for later execution
- if (!scriptLoadedCallbacks[url]) {
- scriptLoadedCallbacks[url] = [];
- }
- scriptLoadedCallbacks[url].push({
- func: callback,
- scope: scope || this
- });
- }
- };
- this.remove = function(url) {
- delete states[url];
- delete scriptLoadedCallbacks[url];
- };
- /**
- * Starts the loading of the queue.
- *
- * @method loadQueue
- * @param {function} callback Optional callback to execute when all queued items are loaded.
- * @param {Object} scope Optional scope to execute the callback in.
- */
- this.loadQueue = function(callback, scope) {
- this.loadScripts(queue, callback, scope);
- };
- /**
- * Loads the specified queue of files and executes the callback ones they are loaded.
- * This method is generally not used outside this class but it might be useful in some scenarios.
- *
- * @method loadScripts
- * @param {Array} scripts Array of queue items to load.
- * @param {function} callback Optional callback to execute ones all items are loaded.
- * @param {Object} scope Optional scope to execute callback in.
- */
- this.loadScripts = function(scripts, callback, scope) {
- var loadScripts;
- function execScriptLoadedCallbacks(url) {
- // Execute URL callback functions
- each(scriptLoadedCallbacks[url], function(callback) {
- callback.func.call(callback.scope);
- });
- scriptLoadedCallbacks[url] = undef;
- }
- queueLoadedCallbacks.push({
- func: callback,
- scope: scope || this
- });
- loadScripts = function() {
- var loadingScripts = grep(scripts);
- // Current scripts has been handled
- scripts.length = 0;
- // Load scripts that needs to be loaded
- each(loadingScripts, function(url) {
- // Script is already loaded then execute script callbacks directly
- if (states[url] == LOADED) {
- execScriptLoadedCallbacks(url);
- return;
- }
- // Is script not loading then start loading it
- if (states[url] != LOADING) {
- states[url] = LOADING;
- loading++;
- loadScript(url, function() {
- states[url] = LOADED;
- loading--;
- execScriptLoadedCallbacks(url);
- // Load more scripts if they where added by the recently loaded script
- loadScripts();
- });
- }
- });
- // No scripts are currently loading then execute all pending queue loaded callbacks
- if (!loading) {
- each(queueLoadedCallbacks, function(callback) {
- callback.func.call(callback.scope);
- });
- queueLoadedCallbacks.length = 0;
- }
- };
- loadScripts();
- };
- }
- ScriptLoader.ScriptLoader = new ScriptLoader();
- return ScriptLoader;
- });
- // Included from: js/tinymce/classes/AddOnManager.js
- /**
- * AddOnManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles the loading of themes/plugins or other add-ons and their language packs.
- *
- * @class tinymce.AddOnManager
- */
- define("tinymce/AddOnManager", [
- "tinymce/dom/ScriptLoader",
- "tinymce/util/Tools"
- ], function(ScriptLoader, Tools) {
- var each = Tools.each;
- function AddOnManager() {
- var self = this;
- self.items = [];
- self.urls = {};
- self.lookup = {};
- }
- AddOnManager.prototype = {
- /**
- * Returns the specified add on by the short name.
- *
- * @method get
- * @param {String} name Add-on to look for.
- * @return {tinymce.Theme/tinymce.Plugin} Theme or plugin add-on instance or undefined.
- */
- get: function(name) {
- if (this.lookup[name]) {
- return this.lookup[name].instance;
- }
- return undefined;
- },
- dependencies: function(name) {
- var result;
- if (this.lookup[name]) {
- result = this.lookup[name].dependencies;
- }
- return result || [];
- },
- /**
- * Loads a language pack for the specified add-on.
- *
- * @method requireLangPack
- * @param {String} name Short name of the add-on.
- * @param {String} languages Optional comma or space separated list of languages to check if it matches the name.
- */
- requireLangPack: function(name, languages) {
- var language = AddOnManager.language;
- if (language && AddOnManager.languageLoad !== false) {
- if (languages) {
- languages = ',' + languages + ',';
- // Load short form sv.js or long form sv_SE.js
- if (languages.indexOf(',' + language.substr(0, 2) + ',') != -1) {
- language = language.substr(0, 2);
- } else if (languages.indexOf(',' + language + ',') == -1) {
- return;
- }
- }
- ScriptLoader.ScriptLoader.add(this.urls[name] + '/langs/' + language + '.js');
- }
- },
- /**
- * Adds a instance of the add-on by it's short name.
- *
- * @method add
- * @param {String} id Short name/id for the add-on.
- * @param {tinymce.Theme/tinymce.Plugin} addOn Theme or plugin to add.
- * @return {tinymce.Theme/tinymce.Plugin} The same theme or plugin instance that got passed in.
- * @example
- * // Create a simple plugin
- * tinymce.create('tinymce.plugins.TestPlugin', {
- * TestPlugin: function(ed, url) {
- * ed.on('click', function(e) {
- * ed.windowManager.alert('Hello World!');
- * });
- * }
- * });
- *
- * // Register plugin using the add method
- * tinymce.PluginManager.add('test', tinymce.plugins.TestPlugin);
- *
- * // Initialize TinyMCE
- * tinymce.init({
- * ...
- * plugins: '-test' // Init the plugin but don't try to load it
- * });
- */
- add: function(id, addOn, dependencies) {
- this.items.push(addOn);
- this.lookup[id] = {instance: addOn, dependencies: dependencies};
- return addOn;
- },
- remove: function(name) {
- delete this.urls[name];
- delete this.lookup[name];
- },
- createUrl: function(baseUrl, dep) {
- if (typeof dep === "object") {
- return dep;
- }
- return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
- },
- /**
- * Add a set of components that will make up the add-on. Using the url of the add-on name as the base url.
- * This should be used in development mode. A new compressor/javascript munger process will ensure that the
- * components are put together into the plugin.js file and compressed correctly.
- *
- * @method addComponents
- * @param {String} pluginName name of the plugin to load scripts from (will be used to get the base url for the plugins).
- * @param {Array} scripts Array containing the names of the scripts to load.
- */
- addComponents: function(pluginName, scripts) {
- var pluginUrl = this.urls[pluginName];
- each(scripts, function(script) {
- ScriptLoader.ScriptLoader.add(pluginUrl + "/" + script);
- });
- },
- /**
- * Loads an add-on from a specific url.
- *
- * @method load
- * @param {String} name Short name of the add-on that gets loaded.
- * @param {String} addOnUrl URL to the add-on that will get loaded.
- * @param {function} callback Optional callback to execute ones the add-on is loaded.
- * @param {Object} scope Optional scope to execute the callback in.
- * @example
- * // Loads a plugin from an external URL
- * tinymce.PluginManager.load('myplugin', '/some/dir/someplugin/plugin.js');
- *
- * // Initialize TinyMCE
- * tinymce.init({
- * ...
- * plugins: '-myplugin' // Don't try to load it again
- * });
- */
- load: function(name, addOnUrl, callback, scope) {
- var self = this, url = addOnUrl;
- function loadDependencies() {
- var dependencies = self.dependencies(name);
- each(dependencies, function(dep) {
- var newUrl = self.createUrl(addOnUrl, dep);
- self.load(newUrl.resource, newUrl, undefined, undefined);
- });
- if (callback) {
- if (scope) {
- callback.call(scope);
- } else {
- callback.call(ScriptLoader);
- }
- }
- }
- if (self.urls[name]) {
- return;
- }
- if (typeof addOnUrl === "object") {
- url = addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
- }
- if (url.indexOf('/') !== 0 && url.indexOf('://') == -1) {
- url = AddOnManager.baseURL + '/' + url;
- }
- self.urls[name] = url.substring(0, url.lastIndexOf('/'));
- if (self.lookup[name]) {
- loadDependencies();
- } else {
- ScriptLoader.ScriptLoader.add(url, loadDependencies, scope);
- }
- }
- };
- AddOnManager.PluginManager = new AddOnManager();
- AddOnManager.ThemeManager = new AddOnManager();
- return AddOnManager;
- });
- /**
- * TinyMCE theme class.
- *
- * @class tinymce.Theme
- */
- /**
- * This method is responsible for rendering/generating the overall user interface with toolbars, buttons, iframe containers etc.
- *
- * @method renderUI
- * @param {Object} obj Object parameter containing the targetNode DOM node that will be replaced visually with an editor instance.
- * @return {Object} an object with items like iframeContainer, editorContainer, sizeContainer, deltaWidth, deltaHeight.
- */
- /**
- * 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.
- *
- * @class tinymce.Plugin
- * @example
- * tinymce.PluginManager.add('example', function(editor, url) {
- * // Add a button that opens a window
- * editor.addButton('example', {
- * text: 'My button',
- * icon: false,
- * onclick: function() {
- * // Open window
- * editor.windowManager.open({
- * title: 'Example plugin',
- * body: [
- * {type: 'textbox', name: 'title', label: 'Title'}
- * ],
- * onsubmit: function(e) {
- * // Insert content when the window form is submitted
- * editor.insertContent('Title: ' + e.data.title);
- * }
- * });
- * }
- * });
- *
- * // Adds a menu item to the tools menu
- * editor.addMenuItem('example', {
- * text: 'Example plugin',
- * context: 'tools',
- * onclick: function() {
- * // Open window with a specific url
- * editor.windowManager.open({
- * title: 'TinyMCE site',
- * url: 'http://www.tinymce.com',
- * width: 800,
- * height: 600,
- * buttons: [{
- * text: 'Close',
- * onclick: 'close'
- * }]
- * });
- * }
- * });
- * });
- */
- // Included from: js/tinymce/classes/dom/NodeType.js
- /**
- * NodeType.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Contains various node validation functions.
- *
- * @private
- * @class tinymce.dom.NodeType
- */
- define("tinymce/dom/NodeType", [], function() {
- function isNodeType(type) {
- return function(node) {
- return !!node && node.nodeType == type;
- };
- }
- var isElement = isNodeType(1);
- function matchNodeNames(names) {
- names = names.toLowerCase().split(' ');
- return function(node) {
- var i, name;
- if (node && node.nodeType) {
- name = node.nodeName.toLowerCase();
- for (i = 0; i < names.length; i++) {
- if (name === names[i]) {
- return true;
- }
- }
- }
- return false;
- };
- }
- function matchStyleValues(name, values) {
- values = values.toLowerCase().split(' ');
- return function(node) {
- var i, cssValue;
- if (isElement(node)) {
- for (i = 0; i < values.length; i++) {
- cssValue = getComputedStyle(node, null).getPropertyValue(name);
- if (cssValue === values[i]) {
- return true;
- }
- }
- }
- return false;
- };
- }
- function hasPropValue(propName, propValue) {
- return function(node) {
- return isElement(node) && node[propName] === propValue;
- };
- }
- function hasAttributeValue(attrName, attrValue) {
- return function(node) {
- return isElement(node) && node.getAttribute(attrName) === attrValue;
- };
- }
- function isBogus(node) {
- return isElement(node) && node.hasAttribute('data-mce-bogus');
- }
- function hasContentEditableState(value) {
- return function(node) {
- if (isElement(node)) {
- if (node.contentEditable === value) {
- return true;
- }
- if (node.getAttribute('data-mce-contenteditable') === value) {
- return true;
- }
- }
- return false;
- };
- }
- return {
- isText: isNodeType(3),
- isElement: isElement,
- isComment: isNodeType(8),
- isBr: matchNodeNames('br'),
- isContentEditableTrue: hasContentEditableState('true'),
- isContentEditableFalse: hasContentEditableState('false'),
- matchNodeNames: matchNodeNames,
- hasPropValue: hasPropValue,
- hasAttributeValue: hasAttributeValue,
- matchStyleValues: matchStyleValues,
- isBogus: isBogus
- };
- });
- // Included from: js/tinymce/classes/text/Zwsp.js
- /**
- * Zwsp.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * ....
- *
- * @private
- * @class tinymce.text.Zwsp
- * @example
- * var isZwsp = Zwsp.isZwsp('\u200b');
- * var abc = Zwsp.trim('a\u200bc');
- */
- define("tinymce/text/Zwsp", [], function() {
- var ZWSP = '\u200b';
- function isZwsp(chr) {
- return chr == ZWSP;
- }
- function trim(str) {
- return str.replace(new RegExp(ZWSP, 'g'), '');
- }
- return {
- isZwsp: isZwsp,
- ZWSP: ZWSP,
- trim: trim
- };
- });
- // Included from: js/tinymce/classes/caret/CaretContainer.js
- /**
- * CaretContainer.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module handles caret containers. A caret container is a node that
- * holds the caret for positional purposes.
- *
- * @private
- * @class tinymce.caret.CaretContainer
- */
- define("tinymce/caret/CaretContainer", [
- "tinymce/dom/NodeType",
- "tinymce/text/Zwsp"
- ], function(NodeType, Zwsp) {
- var isElement = NodeType.isElement,
- isText = NodeType.isText;
- function isCaretContainerBlock(node) {
- if (isText(node)) {
- node = node.parentNode;
- }
- return isElement(node) && node.hasAttribute('data-mce-caret');
- }
- function isCaretContainerInline(node) {
- return isText(node) && Zwsp.isZwsp(node.data);
- }
- function isCaretContainer(node) {
- return isCaretContainerBlock(node) || isCaretContainerInline(node);
- }
- function insertInline(node, before) {
- var doc, sibling, textNode, parentNode;
- doc = node.ownerDocument;
- textNode = doc.createTextNode(Zwsp.ZWSP);
- parentNode = node.parentNode;
- if (!before) {
- sibling = node.nextSibling;
- if (isText(sibling)) {
- if (isCaretContainer(sibling)) {
- return sibling;
- }
- if (startsWithCaretContainer(sibling)) {
- sibling.splitText(1);
- return sibling;
- }
- }
- if (node.nextSibling) {
- parentNode.insertBefore(textNode, node.nextSibling);
- } else {
- parentNode.appendChild(textNode);
- }
- } else {
- sibling = node.previousSibling;
- if (isText(sibling)) {
- if (isCaretContainer(sibling)) {
- return sibling;
- }
- if (endsWithCaretContainer(sibling)) {
- return sibling.splitText(sibling.data.length - 1);
- }
- }
- parentNode.insertBefore(textNode, node);
- }
- return textNode;
- }
- function insertBlock(blockName, node, before) {
- var doc, blockNode, parentNode;
- doc = node.ownerDocument;
- blockNode = doc.createElement(blockName);
- blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after');
- blockNode.setAttribute('data-mce-bogus', 'all');
- blockNode.appendChild(doc.createTextNode('\u00a0'));
- parentNode = node.parentNode;
- if (!before) {
- if (node.nextSibling) {
- parentNode.insertBefore(blockNode, node.nextSibling);
- } else {
- parentNode.appendChild(blockNode);
- }
- } else {
- parentNode.insertBefore(blockNode, node);
- }
- return blockNode;
- }
- function remove(caretContainerNode) {
- var text;
- if (isElement(caretContainerNode) && isCaretContainer(caretContainerNode)) {
- if (caretContainerNode.innerHTML != ' ') {
- caretContainerNode.removeAttribute('data-mce-caret');
- } else {
- if (caretContainerNode.parentNode) {
- caretContainerNode.parentNode.removeChild(caretContainerNode);
- }
- }
- }
- if (isText(caretContainerNode)) {
- text = Zwsp.trim(caretContainerNode.data);
- if (text.length === 0) {
- if (caretContainerNode.parentNode) {
- caretContainerNode.parentNode.removeChild(caretContainerNode);
- }
- }
- caretContainerNode.nodeValue = text;
- }
- }
- function startsWithCaretContainer(node) {
- return isText(node) && node.data[0] == Zwsp.ZWSP;
- }
- function endsWithCaretContainer(node) {
- return isText(node) && node.data[node.data.length - 1] == Zwsp.ZWSP;
- }
- return {
- isCaretContainer: isCaretContainer,
- isCaretContainerBlock: isCaretContainerBlock,
- isCaretContainerInline: isCaretContainerInline,
- insertInline: insertInline,
- insertBlock: insertBlock,
- remove: remove,
- startsWithCaretContainer: startsWithCaretContainer,
- endsWithCaretContainer: endsWithCaretContainer
- };
- });
- // Included from: js/tinymce/classes/dom/RangeUtils.js
- /**
- * RangeUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class contains a few utility methods for ranges.
- *
- * @class tinymce.dom.RangeUtils
- */
- define("tinymce/dom/RangeUtils", [
- "tinymce/util/Tools",
- "tinymce/dom/TreeWalker",
- "tinymce/dom/NodeType",
- "tinymce/dom/Range",
- "tinymce/caret/CaretContainer"
- ], function(Tools, TreeWalker, NodeType, Range, CaretContainer) {
- var each = Tools.each,
- isContentEditableFalse = NodeType.isContentEditableFalse,
- isCaretContainer = CaretContainer.isCaretContainer;
- function getEndChild(container, index) {
- var childNodes = container.childNodes;
- index--;
- if (index > childNodes.length - 1) {
- index = childNodes.length - 1;
- } else if (index < 0) {
- index = 0;
- }
- return childNodes[index] || container;
- }
- function RangeUtils(dom) {
- /**
- * Walks the specified range like object and executes the callback for each sibling collection it finds.
- *
- * @private
- * @method walk
- * @param {Object} rng Range like object.
- * @param {function} callback Callback function to execute for each sibling collection.
- */
- this.walk = function(rng, callback) {
- var startContainer = rng.startContainer,
- startOffset = rng.startOffset,
- endContainer = rng.endContainer,
- endOffset = rng.endOffset,
- ancestor, startPoint,
- endPoint, node, parent, siblings, nodes;
- // Handle table cell selection the table plugin enables
- // you to fake select table cells and perform formatting actions on them
- nodes = dom.select('td[data-mce-selected],th[data-mce-selected]');
- if (nodes.length > 0) {
- each(nodes, function(node) {
- callback([node]);
- });
- return;
- }
- /**
- * Excludes start/end text node if they are out side the range
- *
- * @private
- * @param {Array} nodes Nodes to exclude items from.
- * @return {Array} Array with nodes excluding the start/end container if needed.
- */
- function exclude(nodes) {
- var node;
- // First node is excluded
- node = nodes[0];
- if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
- nodes.splice(0, 1);
- }
- // Last node is excluded
- node = nodes[nodes.length - 1];
- if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
- nodes.splice(nodes.length - 1, 1);
- }
- return nodes;
- }
- /**
- * Collects siblings
- *
- * @private
- * @param {Node} node Node to collect siblings from.
- * @param {String} name Name of the sibling to check for.
- * @param {Node} end_node
- * @return {Array} Array of collected siblings.
- */
- function collectSiblings(node, name, end_node) {
- var siblings = [];
- for (; node && node != end_node; node = node[name]) {
- siblings.push(node);
- }
- return siblings;
- }
- /**
- * Find an end point this is the node just before the common ancestor root.
- *
- * @private
- * @param {Node} node Node to start at.
- * @param {Node} root Root/ancestor element to stop just before.
- * @return {Node} Node just before the root element.
- */
- function findEndPoint(node, root) {
- do {
- if (node.parentNode == root) {
- return node;
- }
- node = node.parentNode;
- } while (node);
- }
- function walkBoundary(start_node, end_node, next) {
- var siblingName = next ? 'nextSibling' : 'previousSibling';
- for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
- parent = node.parentNode;
- siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
- if (siblings.length) {
- if (!next) {
- siblings.reverse();
- }
- callback(exclude(siblings));
- }
- }
- }
- // If index based start position then resolve it
- if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
- startContainer = startContainer.childNodes[startOffset];
- }
- // If index based end position then resolve it
- if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
- endContainer = getEndChild(endContainer, endOffset);
- }
- // Same container
- if (startContainer == endContainer) {
- return callback(exclude([startContainer]));
- }
- // Find common ancestor and end points
- ancestor = dom.findCommonAncestor(startContainer, endContainer);
- // Process left side
- for (node = startContainer; node; node = node.parentNode) {
- if (node === endContainer) {
- return walkBoundary(startContainer, ancestor, true);
- }
- if (node === ancestor) {
- break;
- }
- }
- // Process right side
- for (node = endContainer; node; node = node.parentNode) {
- if (node === startContainer) {
- return walkBoundary(endContainer, ancestor);
- }
- if (node === ancestor) {
- break;
- }
- }
- // Find start/end point
- startPoint = findEndPoint(startContainer, ancestor) || startContainer;
- endPoint = findEndPoint(endContainer, ancestor) || endContainer;
- // Walk left leaf
- walkBoundary(startContainer, startPoint, true);
- // Walk the middle from start to end point
- siblings = collectSiblings(
- startPoint == startContainer ? startPoint : startPoint.nextSibling,
- 'nextSibling',
- endPoint == endContainer ? endPoint.nextSibling : endPoint
- );
- if (siblings.length) {
- callback(exclude(siblings));
- }
- // Walk right leaf
- walkBoundary(endContainer, endPoint);
- };
- /**
- * Splits the specified range at it's start/end points.
- *
- * @private
- * @param {Range/RangeObject} rng Range to split.
- * @return {Object} Range position object.
- */
- this.split = function(rng) {
- var startContainer = rng.startContainer,
- startOffset = rng.startOffset,
- endContainer = rng.endContainer,
- endOffset = rng.endOffset;
- function splitText(node, offset) {
- return node.splitText(offset);
- }
- // Handle single text node
- if (startContainer == endContainer && startContainer.nodeType == 3) {
- if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
- endContainer = splitText(startContainer, startOffset);
- startContainer = endContainer.previousSibling;
- if (endOffset > startOffset) {
- endOffset = endOffset - startOffset;
- startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
- endOffset = endContainer.nodeValue.length;
- startOffset = 0;
- } else {
- endOffset = 0;
- }
- }
- } else {
- // Split startContainer text node if needed
- if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
- startContainer = splitText(startContainer, startOffset);
- startOffset = 0;
- }
- // Split endContainer text node if needed
- if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
- endContainer = splitText(endContainer, endOffset).previousSibling;
- endOffset = endContainer.nodeValue.length;
- }
- }
- return {
- startContainer: startContainer,
- startOffset: startOffset,
- endContainer: endContainer,
- endOffset: endOffset
- };
- };
- /**
- * Normalizes the specified range by finding the closest best suitable caret location.
- *
- * @private
- * @param {Range} rng Range to normalize.
- * @return {Boolean} True/false if the specified range was normalized or not.
- */
- this.normalize = function(rng) {
- var normalized, collapsed;
- function normalizeEndPoint(start) {
- var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap;
- var directionLeft, isAfterNode;
- function isTableCell(node) {
- return node && /^(TD|TH|CAPTION)$/.test(node.nodeName);
- }
- function hasBrBeforeAfter(node, left) {
- var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
- while ((node = walker[left ? 'prev' : 'next']())) {
- if (node.nodeName === "BR") {
- return true;
- }
- }
- }
- function hasContentEditableFalseParent(node) {
- while (node && node != body) {
- if (isContentEditableFalse(node)) {
- return true;
- }
- node = node.parentNode;
- }
- return false;
- }
- function isPrevNode(node, name) {
- return node.previousSibling && node.previousSibling.nodeName == name;
- }
- // Walks the dom left/right to find a suitable text node to move the endpoint into
- // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
- function findTextNodeRelative(left, startNode) {
- var walker, lastInlineElement, parentBlockContainer;
- startNode = startNode || container;
- parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
- // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
- // This: <p><br>|</p> becomes <p>|<br></p>
- if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
- container = startNode.parentNode;
- offset = dom.nodeIndex(startNode);
- normalized = true;
- return;
- }
- // Walk left until we hit a text node we can move to or a block/br/img
- walker = new TreeWalker(startNode, parentBlockContainer);
- while ((node = walker[left ? 'prev' : 'next']())) {
- // Break if we hit a non content editable node
- if (dom.getContentEditableParent(node) === "false" || isCaretContainer(node)) {
- return;
- }
- // Found text node that has a length
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
- container = node;
- offset = left ? node.nodeValue.length : 0;
- normalized = true;
- return;
- }
- // Break if we find a block or a BR/IMG/INPUT etc
- if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
- return;
- }
- lastInlineElement = node;
- }
- // Only fetch the last inline element when in caret mode for now
- if (collapsed && lastInlineElement) {
- container = lastInlineElement;
- normalized = true;
- offset = 0;
- }
- }
- container = rng[(start ? 'start' : 'end') + 'Container'];
- offset = rng[(start ? 'start' : 'end') + 'Offset'];
- isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
- nonEmptyElementsMap = dom.schema.getNonEmptyElements();
- directionLeft = start;
- if (isCaretContainer(container)) {
- return;
- }
- if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
- directionLeft = false;
- }
- // If the container is a document move it to the body element
- if (container.nodeType === 9) {
- container = dom.getRoot();
- offset = 0;
- }
- // If the container is body try move it into the closest text node or position
- if (container === body) {
- // If start is before/after a image, table etc
- if (directionLeft) {
- node = container.childNodes[offset > 0 ? offset - 1 : 0];
- if (node) {
- if (isCaretContainer(node)) {
- return;
- }
- if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
- return;
- }
- }
- }
- // Resolve the index
- if (container.hasChildNodes()) {
- offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
- container = container.childNodes[offset];
- offset = 0;
- // Don't normalize non collapsed selections like <p>[a</p><table></table>]
- if (!collapsed && container === body.lastChild && container.nodeName === 'TABLE') {
- return;
- }
- if (hasContentEditableFalseParent(container) || isCaretContainer(container)) {
- return;
- }
- // Don't walk into elements that doesn't have any child nodes like a IMG
- if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
- // Walk the DOM to find a text node to place the caret at or a BR
- node = container;
- walker = new TreeWalker(container, body);
- do {
- if (isContentEditableFalse(node) || isCaretContainer(node)) {
- normalized = false;
- break;
- }
- // Found a text node use that position
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
- offset = directionLeft ? 0 : node.nodeValue.length;
- container = node;
- normalized = true;
- break;
- }
- // Found a BR/IMG element that we can place the caret before
- if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCell(node)) {
- offset = dom.nodeIndex(node);
- container = node.parentNode;
- // Put caret after image when moving the end point
- if (node.nodeName == "IMG" && !directionLeft) {
- offset++;
- }
- normalized = true;
- break;
- }
- } while ((node = (directionLeft ? walker.next() : walker.prev())));
- }
- }
- }
- // Lean the caret to the left if possible
- if (collapsed) {
- // So this: <b>x</b><i>|x</i>
- // Becomes: <b>x|</b><i>x</i>
- // Seems that only gecko has issues with this
- if (container.nodeType === 3 && offset === 0) {
- findTextNodeRelative(true);
- }
- // Lean left into empty inline elements when the caret is before a BR
- // So this: <i><b></b><i>|<br></i>
- // Becomes: <i><b>|</b><i><br></i>
- // Seems that only gecko has issues with this.
- // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
- if (container.nodeType === 1) {
- node = container.childNodes[offset];
- // Offset is after the containers last child
- // then use the previous child for normalization
- if (!node) {
- node = container.childNodes[offset - 1];
- }
- if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
- !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
- findTextNodeRelative(true, node);
- }
- }
- }
- // Lean the start of the selection right if possible
- // So this: x[<b>x]</b>
- // Becomes: x<b>[x]</b>
- if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
- findTextNodeRelative(false);
- }
- // Set endpoint if it was normalized
- if (normalized) {
- rng['set' + (start ? 'Start' : 'End')](container, offset);
- }
- }
- collapsed = rng.collapsed;
- normalizeEndPoint(true);
- if (!collapsed) {
- normalizeEndPoint();
- }
- // If it was collapsed then make sure it still is
- if (normalized && collapsed) {
- rng.collapse(true);
- }
- return normalized;
- };
- }
- /**
- * Compares two ranges and checks if they are equal.
- *
- * @static
- * @method compareRanges
- * @param {DOMRange} rng1 First range to compare.
- * @param {DOMRange} rng2 First range to compare.
- * @return {Boolean} true/false if the ranges are equal.
- */
- RangeUtils.compareRanges = function(rng1, rng2) {
- if (rng1 && rng2) {
- // Compare native IE ranges
- if (rng1.item || rng1.duplicate) {
- // Both are control ranges and the selected element matches
- if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
- return true;
- }
- // Both are text ranges and the range matches
- if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
- return true;
- }
- } else {
- // Compare w3c ranges
- return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
- }
- }
- return false;
- };
- /**
- * Finds the closest selection rect tries to get the range from that.
- */
- function findClosestIeRange(clientX, clientY, doc) {
- var element, rng, rects;
- element = doc.elementFromPoint(clientX, clientY);
- rng = doc.body.createTextRange();
- if (!element || element.tagName == 'HTML') {
- element = doc.body;
- }
- rng.moveToElementText(element);
- rects = Tools.toArray(rng.getClientRects());
- rects = rects.sort(function(a, b) {
- a = Math.abs(Math.max(a.top - clientY, a.bottom - clientY));
- b = Math.abs(Math.max(b.top - clientY, b.bottom - clientY));
- return a - b;
- });
- if (rects.length > 0) {
- clientY = (rects[0].bottom + rects[0].top) / 2;
- try {
- rng.moveToPoint(clientX, clientY);
- rng.collapse(true);
- return rng;
- } catch (ex) {
- // At least we tried
- }
- }
- return null;
- }
- /**
- * Gets the caret range for the given x/y location.
- *
- * @static
- * @method getCaretRangeFromPoint
- * @param {Number} clientX X coordinate for range
- * @param {Number} clientY Y coordinate for range
- * @param {Document} doc Document that x/y are relative to
- * @returns {Range} caret range
- */
- RangeUtils.getCaretRangeFromPoint = function(clientX, clientY, doc) {
- var rng, point;
- if (doc.caretPositionFromPoint) {
- point = doc.caretPositionFromPoint(clientX, clientY);
- rng = doc.createRange();
- rng.setStart(point.offsetNode, point.offset);
- rng.collapse(true);
- } else if (doc.caretRangeFromPoint) {
- rng = doc.caretRangeFromPoint(clientX, clientY);
- } else if (doc.body.createTextRange) {
- rng = doc.body.createTextRange();
- try {
- rng.moveToPoint(clientX, clientY);
- rng.collapse(true);
- } catch (ex) {
- rng = findClosestIeRange(clientX, clientY, doc);
- }
- }
- return rng;
- };
- RangeUtils.getSelectedNode = function(range) {
- var startContainer = range.startContainer,
- startOffset = range.startOffset;
- if (startContainer.hasChildNodes() && range.endOffset == startOffset + 1) {
- return startContainer.childNodes[startOffset];
- }
- return null;
- };
- RangeUtils.getNode = function(container, offset) {
- if (container.nodeType == 1 && container.hasChildNodes()) {
- if (offset >= container.childNodes.length) {
- offset = container.childNodes.length - 1;
- }
- container = container.childNodes[offset];
- }
- return container;
- };
- return RangeUtils;
- });
- // Included from: js/tinymce/classes/NodeChange.js
- /**
- * NodeChange.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles the nodechange event dispatching both manual and through selection change events.
- *
- * @class tinymce.NodeChange
- * @private
- */
- define("tinymce/NodeChange", [
- "tinymce/dom/RangeUtils",
- "tinymce/Env",
- "tinymce/util/Delay"
- ], function(RangeUtils, Env, Delay) {
- return function(editor) {
- var lastRng, lastPath = [];
- /**
- * Returns true/false if the current element path has been changed or not.
- *
- * @private
- * @return {Boolean} True if the element path is the same false if it's not.
- */
- function isSameElementPath(startElm) {
- var i, currentPath;
- currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm);
- if (currentPath.length === lastPath.length) {
- for (i = currentPath.length; i >= 0; i--) {
- if (currentPath[i] !== lastPath[i]) {
- break;
- }
- }
- if (i === -1) {
- lastPath = currentPath;
- return true;
- }
- }
- lastPath = currentPath;
- return false;
- }
- // Gecko doesn't support the "selectionchange" event
- if (!('onselectionchange' in editor.getDoc())) {
- editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) {
- var nativeRng, fakeRng;
- // Since DOM Ranges mutate on modification
- // of the DOM we need to clone it's contents
- nativeRng = editor.selection.getRng();
- fakeRng = {
- startContainer: nativeRng.startContainer,
- startOffset: nativeRng.startOffset,
- endContainer: nativeRng.endContainer,
- endOffset: nativeRng.endOffset
- };
- // Always treat nodechange as a selectionchange since applying
- // formatting to the current range wouldn't update the range but it's parent
- if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) {
- editor.fire('SelectionChange');
- }
- lastRng = fakeRng;
- });
- }
- // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body
- // When the contextmenu event fires the selection is located at the right location
- editor.on('contextmenu', function() {
- editor.fire('SelectionChange');
- });
- // Selection change is delayed ~200ms on IE when you click inside the current range
- editor.on('SelectionChange', function() {
- var startElm = editor.selection.getStart(true);
- // IE 8 will fire a selectionchange event with an incorrect selection
- // when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event
- if (!Env.range && editor.selection.isCollapsed()) {
- return;
- }
- if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
- editor.nodeChanged({selectionChange: true});
- }
- });
- // Fire an extra nodeChange on mouseup for compatibility reasons
- editor.on('MouseUp', function(e) {
- if (!e.isDefaultPrevented()) {
- // Delay nodeChanged call for WebKit edge case issue where the range
- // isn't updated until after you click outside a selected image
- if (editor.selection.getNode().nodeName == 'IMG') {
- Delay.setEditorTimeout(editor, function() {
- editor.nodeChanged();
- });
- } else {
- editor.nodeChanged();
- }
- }
- });
- /**
- * Dispatches out a onNodeChange event to all observers. This method should be called when you
- * need to update the UI states or element path etc.
- *
- * @method nodeChanged
- * @param {Object} args Optional args to pass to NodeChange event handlers.
- */
- this.nodeChanged = function(args) {
- var selection = editor.selection, node, parents, root;
- // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
- if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.readonly) {
- // Get start node
- root = editor.getBody();
- node = selection.getStart() || root;
- // Make sure the node is within the editor root or is the editor root
- if (node.ownerDocument != editor.getDoc() || !editor.dom.isChildOf(node, root)) {
- node = root;
- }
- // Edge case for <p>|<img></p>
- if (node.nodeName == 'IMG' && selection.isCollapsed()) {
- node = node.parentNode;
- }
- // Get parents and add them to object
- parents = [];
- editor.dom.getParent(node, function(node) {
- if (node === root) {
- return true;
- }
- parents.push(node);
- });
- args = args || {};
- args.element = node;
- args.parents = parents;
- editor.fire('NodeChange', args);
- }
- };
- };
- });
- // Included from: js/tinymce/classes/html/Node.js
- /**
- * Node.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is a minimalistic implementation of a DOM like node used by the DomParser class.
- *
- * @example
- * var node = new tinymce.html.Node('strong', 1);
- * someRoot.append(node);
- *
- * @class tinymce.html.Node
- * @version 3.4
- */
- define("tinymce/html/Node", [], function() {
- var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
- '#text': 3,
- '#comment': 8,
- '#cdata': 4,
- '#pi': 7,
- '#doctype': 10,
- '#document-fragment': 11
- };
- // Walks the tree left/right
- function walk(node, root_node, prev) {
- var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
- // Walk into nodes if it has a start
- if (node[startName]) {
- return node[startName];
- }
- // Return the sibling if it has one
- if (node !== root_node) {
- sibling = node[siblingName];
- if (sibling) {
- return sibling;
- }
- // Walk up the parents to look for siblings
- for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
- sibling = parent[siblingName];
- if (sibling) {
- return sibling;
- }
- }
- }
- }
- /**
- * Constructs a new Node instance.
- *
- * @constructor
- * @method Node
- * @param {String} name Name of the node type.
- * @param {Number} type Numeric type representing the node.
- */
- function Node(name, type) {
- this.name = name;
- this.type = type;
- if (type === 1) {
- this.attributes = [];
- this.attributes.map = {};
- }
- }
- Node.prototype = {
- /**
- * Replaces the current node with the specified one.
- *
- * @example
- * someNode.replace(someNewNode);
- *
- * @method replace
- * @param {tinymce.html.Node} node Node to replace the current node with.
- * @return {tinymce.html.Node} The old node that got replaced.
- */
- replace: function(node) {
- var self = this;
- if (node.parent) {
- node.remove();
- }
- self.insert(node, self);
- self.remove();
- return self;
- },
- /**
- * Gets/sets or removes an attribute by name.
- *
- * @example
- * someNode.attr("name", "value"); // Sets an attribute
- * console.log(someNode.attr("name")); // Gets an attribute
- * someNode.attr("name", null); // Removes an attribute
- *
- * @method attr
- * @param {String} name Attribute name to set or get.
- * @param {String} value Optional value to set.
- * @return {String/tinymce.html.Node} String or undefined on a get operation or the current node on a set operation.
- */
- attr: function(name, value) {
- var self = this, attrs, i, undef;
- if (typeof name !== "string") {
- for (i in name) {
- self.attr(i, name[i]);
- }
- return self;
- }
- if ((attrs = self.attributes)) {
- if (value !== undef) {
- // Remove attribute
- if (value === null) {
- if (name in attrs.map) {
- delete attrs.map[name];
- i = attrs.length;
- while (i--) {
- if (attrs[i].name === name) {
- attrs = attrs.splice(i, 1);
- return self;
- }
- }
- }
- return self;
- }
- // Set attribute
- if (name in attrs.map) {
- // Set attribute
- i = attrs.length;
- while (i--) {
- if (attrs[i].name === name) {
- attrs[i].value = value;
- break;
- }
- }
- } else {
- attrs.push({name: name, value: value});
- }
- attrs.map[name] = value;
- return self;
- }
- return attrs.map[name];
- }
- },
- /**
- * Does a shallow clones the node into a new node. It will also exclude id attributes since
- * there should only be one id per document.
- *
- * @example
- * var clonedNode = node.clone();
- *
- * @method clone
- * @return {tinymce.html.Node} New copy of the original node.
- */
- clone: function() {
- var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
- // Clone element attributes
- if ((selfAttrs = self.attributes)) {
- cloneAttrs = [];
- cloneAttrs.map = {};
- for (i = 0, l = selfAttrs.length; i < l; i++) {
- selfAttr = selfAttrs[i];
- // Clone everything except id
- if (selfAttr.name !== 'id') {
- cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
- cloneAttrs.map[selfAttr.name] = selfAttr.value;
- }
- }
- clone.attributes = cloneAttrs;
- }
- clone.value = self.value;
- clone.shortEnded = self.shortEnded;
- return clone;
- },
- /**
- * Wraps the node in in another node.
- *
- * @example
- * node.wrap(wrapperNode);
- *
- * @method wrap
- */
- wrap: function(wrapper) {
- var self = this;
- self.parent.insert(wrapper, self);
- wrapper.append(self);
- return self;
- },
- /**
- * Unwraps the node in other words it removes the node but keeps the children.
- *
- * @example
- * node.unwrap();
- *
- * @method unwrap
- */
- unwrap: function() {
- var self = this, node, next;
- for (node = self.firstChild; node;) {
- next = node.next;
- self.insert(node, self, true);
- node = next;
- }
- self.remove();
- },
- /**
- * Removes the node from it's parent.
- *
- * @example
- * node.remove();
- *
- * @method remove
- * @return {tinymce.html.Node} Current node that got removed.
- */
- remove: function() {
- var self = this, parent = self.parent, next = self.next, prev = self.prev;
- if (parent) {
- if (parent.firstChild === self) {
- parent.firstChild = next;
- if (next) {
- next.prev = null;
- }
- } else {
- prev.next = next;
- }
- if (parent.lastChild === self) {
- parent.lastChild = prev;
- if (prev) {
- prev.next = null;
- }
- } else {
- next.prev = prev;
- }
- self.parent = self.next = self.prev = null;
- }
- return self;
- },
- /**
- * Appends a new node as a child of the current node.
- *
- * @example
- * node.append(someNode);
- *
- * @method append
- * @param {tinymce.html.Node} node Node to append as a child of the current one.
- * @return {tinymce.html.Node} The node that got appended.
- */
- append: function(node) {
- var self = this, last;
- if (node.parent) {
- node.remove();
- }
- last = self.lastChild;
- if (last) {
- last.next = node;
- node.prev = last;
- self.lastChild = node;
- } else {
- self.lastChild = self.firstChild = node;
- }
- node.parent = self;
- return node;
- },
- /**
- * Inserts a node at a specific position as a child of the current node.
- *
- * @example
- * parentNode.insert(newChildNode, oldChildNode);
- *
- * @method insert
- * @param {tinymce.html.Node} node Node to insert as a child of the current node.
- * @param {tinymce.html.Node} ref_node Reference node to set node before/after.
- * @param {Boolean} before Optional state to insert the node before the reference node.
- * @return {tinymce.html.Node} The node that got inserted.
- */
- insert: function(node, ref_node, before) {
- var parent;
- if (node.parent) {
- node.remove();
- }
- parent = ref_node.parent || this;
- if (before) {
- if (ref_node === parent.firstChild) {
- parent.firstChild = node;
- } else {
- ref_node.prev.next = node;
- }
- node.prev = ref_node.prev;
- node.next = ref_node;
- ref_node.prev = node;
- } else {
- if (ref_node === parent.lastChild) {
- parent.lastChild = node;
- } else {
- ref_node.next.prev = node;
- }
- node.next = ref_node.next;
- node.prev = ref_node;
- ref_node.next = node;
- }
- node.parent = parent;
- return node;
- },
- /**
- * Get all children by name.
- *
- * @method getAll
- * @param {String} name Name of the child nodes to collect.
- * @return {Array} Array with child nodes matchin the specified name.
- */
- getAll: function(name) {
- var self = this, node, collection = [];
- for (node = self.firstChild; node; node = walk(node, self)) {
- if (node.name === name) {
- collection.push(node);
- }
- }
- return collection;
- },
- /**
- * Removes all children of the current node.
- *
- * @method empty
- * @return {tinymce.html.Node} The current node that got cleared.
- */
- empty: function() {
- var self = this, nodes, i, node;
- // Remove all children
- if (self.firstChild) {
- nodes = [];
- // Collect the children
- for (node = self.firstChild; node; node = walk(node, self)) {
- nodes.push(node);
- }
- // Remove the children
- i = nodes.length;
- while (i--) {
- node = nodes[i];
- node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
- }
- }
- self.firstChild = self.lastChild = null;
- return self;
- },
- /**
- * Returns true/false if the node is to be considered empty or not.
- *
- * @example
- * node.isEmpty({img: true});
- * @method isEmpty
- * @param {Object} elements Name/value object with elements that are automatically treated as non empty elements.
- * @return {Boolean} true/false if the node is empty or not.
- */
- isEmpty: function(elements) {
- var self = this, node = self.firstChild, i, name;
- if (node) {
- do {
- if (node.type === 1) {
- // Ignore bogus elements
- if (node.attributes.map['data-mce-bogus']) {
- continue;
- }
- // Keep empty elements like <img />
- if (elements[node.name]) {
- return false;
- }
- // Keep bookmark nodes and name attribute like <a name="1"></a>
- i = node.attributes.length;
- while (i--) {
- name = node.attributes[i].name;
- if (name === "name" || name.indexOf('data-mce-bookmark') === 0) {
- return false;
- }
- }
- }
- // Keep comments
- if (node.type === 8) {
- return false;
- }
- // Keep non whitespace text nodes
- if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) {
- return false;
- }
- } while ((node = walk(node, self)));
- }
- return true;
- },
- /**
- * Walks to the next or previous node and returns that node or null if it wasn't found.
- *
- * @method walk
- * @param {Boolean} prev Optional previous node state defaults to false.
- * @return {tinymce.html.Node} Node that is next to or previous of the current node.
- */
- walk: function(prev) {
- return walk(this, null, prev);
- }
- };
- /**
- * Creates a node of a specific type.
- *
- * @static
- * @method create
- * @param {String} name Name of the node type to create for example "b" or "#text".
- * @param {Object} attrs Name/value collection of attributes that will be applied to elements.
- */
- Node.create = function(name, attrs) {
- var node, attrName;
- // Create node
- node = new Node(name, typeLookup[name] || 1);
- // Add attributes if needed
- if (attrs) {
- for (attrName in attrs) {
- node.attr(attrName, attrs[attrName]);
- }
- }
- return node;
- };
- return Node;
- });
- // Included from: js/tinymce/classes/html/Schema.js
- /**
- * Schema.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Schema validator class.
- *
- * @class tinymce.html.Schema
- * @example
- * if (tinymce.activeEditor.schema.isValidChild('p', 'span'))
- * alert('span is valid child of p.');
- *
- * if (tinymce.activeEditor.schema.getElementRule('p'))
- * alert('P is a valid element.');
- *
- * @class tinymce.html.Schema
- * @version 3.4
- */
- define("tinymce/html/Schema", [
- "tinymce/util/Tools"
- ], function(Tools) {
- var mapCache = {}, dummyObj = {};
- var makeMap = Tools.makeMap, each = Tools.each, extend = Tools.extend, explode = Tools.explode, inArray = Tools.inArray;
- function split(items, delim) {
- return items ? items.split(delim || ' ') : [];
- }
- /**
- * Builds a schema lookup table
- *
- * @private
- * @param {String} type html4, html5 or html5-strict schema type.
- * @return {Object} Schema lookup table.
- */
- function compileSchema(type) {
- var schema = {}, globalAttributes, blockContent;
- var phrasingContent, flowContent, html4BlockContent, html4PhrasingContent;
- function add(name, attributes, children) {
- var ni, i, attributesOrder, args = arguments;
- function arrayToMap(array, obj) {
- var map = {}, i, l;
- for (i = 0, l = array.length; i < l; i++) {
- map[array[i]] = obj || {};
- }
- return map;
- }
- children = children || [];
- attributes = attributes || "";
- if (typeof children === "string") {
- children = split(children);
- }
- // Split string children
- for (i = 3; i < args.length; i++) {
- if (typeof args[i] === "string") {
- args[i] = split(args[i]);
- }
- children.push.apply(children, args[i]);
- }
- name = split(name);
- ni = name.length;
- while (ni--) {
- attributesOrder = [].concat(globalAttributes, split(attributes));
- schema[name[ni]] = {
- attributes: arrayToMap(attributesOrder),
- attributesOrder: attributesOrder,
- children: arrayToMap(children, dummyObj)
- };
- }
- }
- function addAttrs(name, attributes) {
- var ni, schemaItem, i, l;
- name = split(name);
- ni = name.length;
- attributes = split(attributes);
- while (ni--) {
- schemaItem = schema[name[ni]];
- for (i = 0, l = attributes.length; i < l; i++) {
- schemaItem.attributes[attributes[i]] = {};
- schemaItem.attributesOrder.push(attributes[i]);
- }
- }
- }
- // Use cached schema
- if (mapCache[type]) {
- return mapCache[type];
- }
- // Attributes present on all elements
- globalAttributes = split("id accesskey class dir lang style tabindex title");
- // Event attributes can be opt-in/opt-out
- /*eventAttributes = split("onabort onblur oncancel oncanplay oncanplaythrough onchange onclick onclose oncontextmenu oncuechange " +
- "ondblclick ondrag ondragend ondragenter ondragleave ondragover ondragstart ondrop ondurationchange onemptied onended " +
- "onerror onfocus oninput oninvalid onkeydown onkeypress onkeyup onload onloadeddata onloadedmetadata onloadstart " +
- "onmousedown onmousemove onmouseout onmouseover onmouseup onmousewheel onpause onplay onplaying onprogress onratechange " +
- "onreset onscroll onseeked onseeking onseeking onselect onshow onstalled onsubmit onsuspend ontimeupdate onvolumechange " +
- "onwaiting"
- );*/
- // Block content elements
- blockContent = split(
- "address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul"
- );
- // Phrasing content elements from the HTML5 spec (inline)
- phrasingContent = split(
- "a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd " +
- "label map noscript object q s samp script select small span strong sub sup " +
- "textarea u var #text #comment"
- );
- // Add HTML5 items to globalAttributes, blockContent, phrasingContent
- if (type != "html4") {
- globalAttributes.push.apply(globalAttributes, split("contenteditable contextmenu draggable dropzone " +
- "hidden spellcheck translate"));
- blockContent.push.apply(blockContent, split("article aside details dialog figure header footer hgroup section nav"));
- phrasingContent.push.apply(phrasingContent, split("audio canvas command datalist mark meter output picture " +
- "progress time wbr video ruby bdi keygen"));
- }
- // Add HTML4 elements unless it's html5-strict
- if (type != "html5-strict") {
- globalAttributes.push("xml:lang");
- html4PhrasingContent = split("acronym applet basefont big font strike tt");
- phrasingContent.push.apply(phrasingContent, html4PhrasingContent);
- each(html4PhrasingContent, function(name) {
- add(name, "", phrasingContent);
- });
- html4BlockContent = split("center dir isindex noframes");
- blockContent.push.apply(blockContent, html4BlockContent);
- // Flow content elements from the HTML5 spec (block+inline)
- flowContent = [].concat(blockContent, phrasingContent);
- each(html4BlockContent, function(name) {
- add(name, "", flowContent);
- });
- }
- // Flow content elements from the HTML5 spec (block+inline)
- flowContent = flowContent || [].concat(blockContent, phrasingContent);
- // HTML4 base schema TODO: Move HTML5 specific attributes to HTML5 specific if statement
- // Schema items <element name>, <specific attributes>, <children ..>
- add("html", "manifest", "head body");
- add("head", "", "base command link meta noscript script style title");
- add("title hr noscript br");
- add("base", "href target");
- add("link", "href rel media hreflang type sizes hreflang");
- add("meta", "name http-equiv content charset");
- add("style", "media type scoped");
- add("script", "src async defer type charset");
- add("body", "onafterprint onbeforeprint onbeforeunload onblur onerror onfocus " +
- "onhashchange onload onmessage onoffline ononline onpagehide onpageshow " +
- "onpopstate onresize onscroll onstorage onunload", flowContent);
- add("address dt dd div caption", "", flowContent);
- 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);
- add("blockquote", "cite", flowContent);
- add("ol", "reversed start type", "li");
- add("ul", "", "li");
- add("li", "value", flowContent);
- add("dl", "", "dt dd");
- add("a", "href target rel media hreflang type", phrasingContent);
- add("q", "cite", phrasingContent);
- add("ins del", "cite datetime", flowContent);
- add("img", "src sizes srcset alt usemap ismap width height");
- add("iframe", "src name width height", flowContent);
- add("embed", "src type width height");
- add("object", "data type typemustmatch name usemap form width height", flowContent, "param");
- add("param", "name value");
- add("map", "name", flowContent, "area");
- add("area", "alt coords shape href target rel media hreflang type");
- add("table", "border", "caption colgroup thead tfoot tbody tr" + (type == "html4" ? " col" : ""));
- add("colgroup", "span", "col");
- add("col", "span");
- add("tbody thead tfoot", "", "tr");
- add("tr", "", "td th");
- add("td", "colspan rowspan headers", flowContent);
- add("th", "colspan rowspan headers scope abbr", flowContent);
- add("form", "accept-charset action autocomplete enctype method name novalidate target", flowContent);
- add("fieldset", "disabled form name", flowContent, "legend");
- add("label", "form for", phrasingContent);
- add("input", "accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate " +
- "formtarget height list max maxlength min multiple name pattern readonly required size src step type value width"
- );
- add("button", "disabled form formaction formenctype formmethod formnovalidate formtarget name type value",
- type == "html4" ? flowContent : phrasingContent);
- add("select", "disabled form multiple name required size", "option optgroup");
- add("optgroup", "disabled label", "option");
- add("option", "disabled label selected value");
- add("textarea", "cols dirname disabled form maxlength name readonly required rows wrap");
- add("menu", "type label", flowContent, "li");
- add("noscript", "", flowContent);
- // Extend with HTML5 elements
- if (type != "html4") {
- add("wbr");
- add("ruby", "", phrasingContent, "rt rp");
- add("figcaption", "", flowContent);
- add("mark rt rp summary bdi", "", phrasingContent);
- add("canvas", "width height", flowContent);
- add("video", "src crossorigin poster preload autoplay mediagroup loop " +
- "muted controls width height buffered", flowContent, "track source");
- add("audio", "src crossorigin preload autoplay mediagroup loop muted controls buffered volume", flowContent, "track source");
- add("picture", "", "img source");
- add("source", "src srcset type media sizes");
- add("track", "kind src srclang label default");
- add("datalist", "", phrasingContent, "option");
- add("article section nav aside header footer", "", flowContent);
- add("hgroup", "", "h1 h2 h3 h4 h5 h6");
- add("figure", "", flowContent, "figcaption");
- add("time", "datetime", phrasingContent);
- add("dialog", "open", flowContent);
- add("command", "type label icon disabled checked radiogroup command");
- add("output", "for form name", phrasingContent);
- add("progress", "value max", phrasingContent);
- add("meter", "value min max low high optimum", phrasingContent);
- add("details", "open", flowContent, "summary");
- add("keygen", "autofocus challenge disabled form keytype name");
- }
- // Extend with HTML4 attributes unless it's html5-strict
- if (type != "html5-strict") {
- addAttrs("script", "language xml:space");
- addAttrs("style", "xml:space");
- addAttrs("object", "declare classid code codebase codetype archive standby align border hspace vspace");
- addAttrs("embed", "align name hspace vspace");
- addAttrs("param", "valuetype type");
- addAttrs("a", "charset name rev shape coords");
- addAttrs("br", "clear");
- addAttrs("applet", "codebase archive code object alt name width height align hspace vspace");
- addAttrs("img", "name longdesc align border hspace vspace");
- addAttrs("iframe", "longdesc frameborder marginwidth marginheight scrolling align");
- addAttrs("font basefont", "size color face");
- addAttrs("input", "usemap align");
- addAttrs("select", "onchange");
- addAttrs("textarea");
- addAttrs("h1 h2 h3 h4 h5 h6 div p legend caption", "align");
- addAttrs("ul", "type compact");
- addAttrs("li", "type");
- addAttrs("ol dl menu dir", "compact");
- addAttrs("pre", "width xml:space");
- addAttrs("hr", "align noshade size width");
- addAttrs("isindex", "prompt");
- addAttrs("table", "summary width frame rules cellspacing cellpadding align bgcolor");
- addAttrs("col", "width align char charoff valign");
- addAttrs("colgroup", "width align char charoff valign");
- addAttrs("thead", "align char charoff valign");
- addAttrs("tr", "align char charoff valign bgcolor");
- addAttrs("th", "axis align char charoff valign nowrap bgcolor width height");
- addAttrs("form", "accept");
- addAttrs("td", "abbr axis scope align char charoff valign nowrap bgcolor width height");
- addAttrs("tfoot", "align char charoff valign");
- addAttrs("tbody", "align char charoff valign");
- addAttrs("area", "nohref");
- addAttrs("body", "background bgcolor text link vlink alink");
- }
- // Extend with HTML5 attributes unless it's html4
- if (type != "html4") {
- addAttrs("input button select textarea", "autofocus");
- addAttrs("input textarea", "placeholder");
- addAttrs("a", "download");
- addAttrs("link script img", "crossorigin");
- addAttrs("iframe", "sandbox seamless allowfullscreen"); // Excluded: srcdoc
- }
- // Special: iframe, ruby, video, audio, label
- // Delete children of the same name from it's parent
- // For example: form can't have a child of the name form
- each(split('a form meter progress dfn'), function(name) {
- if (schema[name]) {
- delete schema[name].children[name];
- }
- });
- // Delete header, footer, sectioning and heading content descendants
- /*each('dt th address', function(name) {
- delete schema[name].children[name];
- });*/
- // Caption can't have tables
- delete schema.caption.children.table;
- // Delete scripts by default due to possible XSS
- delete schema.script;
- // TODO: LI:s can only have value if parent is OL
- // TODO: Handle transparent elements
- // a ins del canvas map
- mapCache[type] = schema;
- return schema;
- }
- function compileElementMap(value, mode) {
- var styles;
- if (value) {
- styles = {};
- if (typeof value == 'string') {
- value = {
- '*': value
- };
- }
- // Convert styles into a rule list
- each(value, function(value, key) {
- styles[key] = styles[key.toUpperCase()] = mode == 'map' ? makeMap(value, /[, ]/) : explode(value, /[, ]/);
- });
- }
- return styles;
- }
- /**
- * Constructs a new Schema instance.
- *
- * @constructor
- * @method Schema
- * @param {Object} settings Name/value settings object.
- */
- return function(settings) {
- var self = this, elements = {}, children = {}, patternElements = [], validStyles, invalidStyles, schemaItems;
- var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, validClasses;
- var blockElementsMap, nonEmptyElementsMap, moveCaretBeforeOnEnterElementsMap, textBlockElementsMap, textInlineElementsMap;
- var customElementsMap = {}, specialElements = {};
- // Creates an lookup table map object for the specified option or the default value
- function createLookupTable(option, default_value, extendWith) {
- var value = settings[option];
- if (!value) {
- // Get cached default map or make it if needed
- value = mapCache[option];
- if (!value) {
- value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
- value = extend(value, extendWith);
- mapCache[option] = value;
- }
- } else {
- // Create custom map
- value = makeMap(value, /[, ]/, makeMap(value.toUpperCase(), /[, ]/));
- }
- return value;
- }
- settings = settings || {};
- schemaItems = compileSchema(settings.schema);
- // Allow all elements and attributes if verify_html is set to false
- if (settings.verify_html === false) {
- settings.valid_elements = '*[*]';
- }
- validStyles = compileElementMap(settings.valid_styles);
- invalidStyles = compileElementMap(settings.invalid_styles, 'map');
- validClasses = compileElementMap(settings.valid_classes, 'map');
- // Setup map objects
- whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object');
- selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
- shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link ' +
- 'meta param embed source wbr track');
- boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' +
- 'noshade nowrap readonly selected autoplay loop controls');
- nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
- moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', 'table', nonEmptyElementsMap);
- textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
- 'blockquote center dir fieldset header footer article section hgroup aside nav figure');
- blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
- 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
- 'datalist select optgroup figcaption', textBlockElementsMap);
- textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font strike u var cite ' +
- 'dfn code mark q sup sub samp');
- each((settings.special || 'script noscript style textarea').split(' '), function(name) {
- specialElements[name] = new RegExp('<\/' + name + '[^>]*>', 'gi');
- });
- // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
- function patternToRegExp(str) {
- return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
- }
- // Parses the specified valid_elements string and adds to the current rules
- // This function is a bit hard to read since it's heavily optimized for speed
- function addValidElements(validElements) {
- var ei, el, ai, al, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
- prefix, outputName, globalAttributes, globalAttributesOrder, key, value,
- elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)\])?$/,
- attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
- hasPatternsRegExp = /[*?+]/;
- if (validElements) {
- // Split valid elements into an array with rules
- validElements = split(validElements, ',');
- if (elements['@']) {
- globalAttributes = elements['@'].attributes;
- globalAttributesOrder = elements['@'].attributesOrder;
- }
- // Loop all rules
- for (ei = 0, el = validElements.length; ei < el; ei++) {
- // Parse element rule
- matches = elementRuleRegExp.exec(validElements[ei]);
- if (matches) {
- // Setup local names for matches
- prefix = matches[1];
- elementName = matches[2];
- outputName = matches[3];
- attrData = matches[5];
- // Create new attributes and attributesOrder
- attributes = {};
- attributesOrder = [];
- // Create the new element
- element = {
- attributes: attributes,
- attributesOrder: attributesOrder
- };
- // Padd empty elements prefix
- if (prefix === '#') {
- element.paddEmpty = true;
- }
- // Remove empty elements prefix
- if (prefix === '-') {
- element.removeEmpty = true;
- }
- if (matches[4] === '!') {
- element.removeEmptyAttrs = true;
- }
- // Copy attributes from global rule into current rule
- if (globalAttributes) {
- for (key in globalAttributes) {
- attributes[key] = globalAttributes[key];
- }
- attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
- }
- // Attributes defined
- if (attrData) {
- attrData = split(attrData, '|');
- for (ai = 0, al = attrData.length; ai < al; ai++) {
- matches = attrRuleRegExp.exec(attrData[ai]);
- if (matches) {
- attr = {};
- attrType = matches[1];
- attrName = matches[2].replace(/::/g, ':');
- prefix = matches[3];
- value = matches[4];
- // Required
- if (attrType === '!') {
- element.attributesRequired = element.attributesRequired || [];
- element.attributesRequired.push(attrName);
- attr.required = true;
- }
- // Denied from global
- if (attrType === '-') {
- delete attributes[attrName];
- attributesOrder.splice(inArray(attributesOrder, attrName), 1);
- continue;
- }
- // Default value
- if (prefix) {
- // Default value
- if (prefix === '=') {
- element.attributesDefault = element.attributesDefault || [];
- element.attributesDefault.push({name: attrName, value: value});
- attr.defaultValue = value;
- }
- // Forced value
- if (prefix === ':') {
- element.attributesForced = element.attributesForced || [];
- element.attributesForced.push({name: attrName, value: value});
- attr.forcedValue = value;
- }
- // Required values
- if (prefix === '<') {
- attr.validValues = makeMap(value, '?');
- }
- }
- // Check for attribute patterns
- if (hasPatternsRegExp.test(attrName)) {
- element.attributePatterns = element.attributePatterns || [];
- attr.pattern = patternToRegExp(attrName);
- element.attributePatterns.push(attr);
- } else {
- // Add attribute to order list if it doesn't already exist
- if (!attributes[attrName]) {
- attributesOrder.push(attrName);
- }
- attributes[attrName] = attr;
- }
- }
- }
- }
- // Global rule, store away these for later usage
- if (!globalAttributes && elementName == '@') {
- globalAttributes = attributes;
- globalAttributesOrder = attributesOrder;
- }
- // Handle substitute elements such as b/strong
- if (outputName) {
- element.outputName = elementName;
- elements[outputName] = element;
- }
- // Add pattern or exact element
- if (hasPatternsRegExp.test(elementName)) {
- element.pattern = patternToRegExp(elementName);
- patternElements.push(element);
- } else {
- elements[elementName] = element;
- }
- }
- }
- }
- }
- function setValidElements(validElements) {
- elements = {};
- patternElements = [];
- addValidElements(validElements);
- each(schemaItems, function(element, name) {
- children[name] = element.children;
- });
- }
- // Adds custom non HTML elements to the schema
- function addCustomElements(customElements) {
- var customElementRegExp = /^(~)?(.+)$/;
- if (customElements) {
- // Flush cached items since we are altering the default maps
- mapCache.text_block_elements = mapCache.block_elements = null;
- each(split(customElements, ','), function(rule) {
- var matches = customElementRegExp.exec(rule),
- inline = matches[1] === '~',
- cloneName = inline ? 'span' : 'div',
- name = matches[2];
- children[name] = children[cloneName];
- customElementsMap[name] = cloneName;
- // If it's not marked as inline then add it to valid block elements
- if (!inline) {
- blockElementsMap[name.toUpperCase()] = {};
- blockElementsMap[name] = {};
- }
- // Add elements clone if needed
- if (!elements[name]) {
- var customRule = elements[cloneName];
- customRule = extend({}, customRule);
- delete customRule.removeEmptyAttrs;
- delete customRule.removeEmpty;
- elements[name] = customRule;
- }
- // Add custom elements at span/div positions
- each(children, function(element, elmName) {
- if (element[cloneName]) {
- children[elmName] = element = extend({}, children[elmName]);
- element[name] = element[cloneName];
- }
- });
- });
- }
- }
- // Adds valid children to the schema object
- function addValidChildren(validChildren) {
- var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
- // Invalidate the schema cache if the schema is mutated
- mapCache[settings.schema] = null;
- if (validChildren) {
- each(split(validChildren, ','), function(rule) {
- var matches = childRuleRegExp.exec(rule), parent, prefix;
- if (matches) {
- prefix = matches[1];
- // Add/remove items from default
- if (prefix) {
- parent = children[matches[2]];
- } else {
- parent = children[matches[2]] = {'#comment': {}};
- }
- parent = children[matches[2]];
- each(split(matches[3], '|'), function(child) {
- if (prefix === '-') {
- delete parent[child];
- } else {
- parent[child] = {};
- }
- });
- }
- });
- }
- }
- function getElementRule(name) {
- var element = elements[name], i;
- // Exact match found
- if (element) {
- return element;
- }
- // No exact match then try the patterns
- i = patternElements.length;
- while (i--) {
- element = patternElements[i];
- if (element.pattern.test(name)) {
- return element;
- }
- }
- }
- if (!settings.valid_elements) {
- // No valid elements defined then clone the elements from the schema spec
- each(schemaItems, function(element, name) {
- elements[name] = {
- attributes: element.attributes,
- attributesOrder: element.attributesOrder
- };
- children[name] = element.children;
- });
- // Switch these on HTML4
- if (settings.schema != "html5") {
- each(split('strong/b em/i'), function(item) {
- item = split(item, '/');
- elements[item[1]].outputName = item[0];
- });
- }
- // Add default alt attribute for images, removed since alt="" is treated as presentational.
- // elements.img.attributesDefault = [{name: 'alt', value: ''}];
- // Remove these if they are empty by default
- each(split('ol ul sub sup blockquote span font a table tbody tr strong em b i'), function(name) {
- if (elements[name]) {
- elements[name].removeEmpty = true;
- }
- });
- // Padd these by default
- each(split('p h1 h2 h3 h4 h5 h6 th td pre div address caption'), function(name) {
- elements[name].paddEmpty = true;
- });
- // Remove these if they have no attributes
- each(split('span'), function(name) {
- elements[name].removeEmptyAttrs = true;
- });
- // Remove these by default
- // TODO: Reenable in 4.1
- /*each(split('script style'), function(name) {
- delete elements[name];
- });*/
- } else {
- setValidElements(settings.valid_elements);
- }
- addCustomElements(settings.custom_elements);
- addValidChildren(settings.valid_children);
- addValidElements(settings.extended_valid_elements);
- // Todo: Remove this when we fix list handling to be valid
- addValidChildren('+ol[ul|ol],+ul[ul|ol]');
- // Delete invalid elements
- if (settings.invalid_elements) {
- each(explode(settings.invalid_elements), function(item) {
- if (elements[item]) {
- delete elements[item];
- }
- });
- }
- // If the user didn't allow span only allow internal spans
- if (!getElementRule('span')) {
- addValidElements('span[!data-mce-type|*]');
- }
- /**
- * Name/value map object with valid parents and children to those parents.
- *
- * @example
- * children = {
- * div:{p:{}, h1:{}}
- * };
- * @field children
- * @type Object
- */
- self.children = children;
- /**
- * Name/value map object with valid styles for each element.
- *
- * @method getValidStyles
- * @type Object
- */
- self.getValidStyles = function() {
- return validStyles;
- };
- /**
- * Name/value map object with valid styles for each element.
- *
- * @method getInvalidStyles
- * @type Object
- */
- self.getInvalidStyles = function() {
- return invalidStyles;
- };
- /**
- * Name/value map object with valid classes for each element.
- *
- * @method getValidClasses
- * @type Object
- */
- self.getValidClasses = function() {
- return validClasses;
- };
- /**
- * Returns a map with boolean attributes.
- *
- * @method getBoolAttrs
- * @return {Object} Name/value lookup map for boolean attributes.
- */
- self.getBoolAttrs = function() {
- return boolAttrMap;
- };
- /**
- * Returns a map with block elements.
- *
- * @method getBlockElements
- * @return {Object} Name/value lookup map for block elements.
- */
- self.getBlockElements = function() {
- return blockElementsMap;
- };
- /**
- * Returns a map with text block elements. Such as: p,h1-h6,div,address
- *
- * @method getTextBlockElements
- * @return {Object} Name/value lookup map for block elements.
- */
- self.getTextBlockElements = function() {
- return textBlockElementsMap;
- };
- /**
- * Returns a map of inline text format nodes for example strong/span or ins.
- *
- * @method getTextInlineElements
- * @return {Object} Name/value lookup map for text format elements.
- */
- self.getTextInlineElements = function() {
- return textInlineElementsMap;
- };
- /**
- * Returns a map with short ended elements such as BR or IMG.
- *
- * @method getShortEndedElements
- * @return {Object} Name/value lookup map for short ended elements.
- */
- self.getShortEndedElements = function() {
- return shortEndedElementsMap;
- };
- /**
- * Returns a map with self closing tags such as <li>.
- *
- * @method getSelfClosingElements
- * @return {Object} Name/value lookup map for self closing tags elements.
- */
- self.getSelfClosingElements = function() {
- return selfClosingElementsMap;
- };
- /**
- * Returns a map with elements that should be treated as contents regardless if it has text
- * content in them or not such as TD, VIDEO or IMG.
- *
- * @method getNonEmptyElements
- * @return {Object} Name/value lookup map for non empty elements.
- */
- self.getNonEmptyElements = function() {
- return nonEmptyElementsMap;
- };
- /**
- * Returns a map with elements that the caret should be moved in front of after enter is
- * pressed
- *
- * @method getMoveCaretBeforeOnEnterElements
- * @return {Object} Name/value lookup map for elements to place the caret in front of.
- */
- self.getMoveCaretBeforeOnEnterElements = function() {
- return moveCaretBeforeOnEnterElementsMap;
- };
- /**
- * Returns a map with elements where white space is to be preserved like PRE or SCRIPT.
- *
- * @method getWhiteSpaceElements
- * @return {Object} Name/value lookup map for white space elements.
- */
- self.getWhiteSpaceElements = function() {
- return whiteSpaceElementsMap;
- };
- /**
- * Returns a map with special elements. These are elements that needs to be parsed
- * in a special way such as script, style, textarea etc. The map object values
- * are regexps used to find the end of the element.
- *
- * @method getSpecialElements
- * @return {Object} Name/value lookup map for special elements.
- */
- self.getSpecialElements = function() {
- return specialElements;
- };
- /**
- * Returns true/false if the specified element and it's child is valid or not
- * according to the schema.
- *
- * @method isValidChild
- * @param {String} name Element name to check for.
- * @param {String} child Element child to verify.
- * @return {Boolean} True/false if the element is a valid child of the specified parent.
- */
- self.isValidChild = function(name, child) {
- var parent = children[name];
- return !!(parent && parent[child]);
- };
- /**
- * Returns true/false if the specified element name and optional attribute is
- * valid according to the schema.
- *
- * @method isValid
- * @param {String} name Name of element to check.
- * @param {String} attr Optional attribute name to check for.
- * @return {Boolean} True/false if the element and attribute is valid.
- */
- self.isValid = function(name, attr) {
- var attrPatterns, i, rule = getElementRule(name);
- // Check if it's a valid element
- if (rule) {
- if (attr) {
- // Check if attribute name exists
- if (rule.attributes[attr]) {
- return true;
- }
- // Check if attribute matches a regexp pattern
- attrPatterns = rule.attributePatterns;
- if (attrPatterns) {
- i = attrPatterns.length;
- while (i--) {
- if (attrPatterns[i].pattern.test(name)) {
- return true;
- }
- }
- }
- } else {
- return true;
- }
- }
- // No match
- return false;
- };
- /**
- * Returns true/false if the specified element is valid or not
- * according to the schema.
- *
- * @method getElementRule
- * @param {String} name Element name to check for.
- * @return {Object} Element object or undefined if the element isn't valid.
- */
- self.getElementRule = getElementRule;
- /**
- * Returns an map object of all custom elements.
- *
- * @method getCustomElements
- * @return {Object} Name/value map object of all custom elements.
- */
- self.getCustomElements = function() {
- return customElementsMap;
- };
- /**
- * Parses a valid elements string and adds it to the schema. The valid elements
- * format is for example "element[attr=default|otherattr]".
- * Existing rules will be replaced with the ones specified, so this extends the schema.
- *
- * @method addValidElements
- * @param {String} valid_elements String in the valid elements format to be parsed.
- */
- self.addValidElements = addValidElements;
- /**
- * Parses a valid elements string and sets it to the schema. The valid elements
- * format is for example "element[attr=default|otherattr]".
- * Existing rules will be replaced with the ones specified, so this extends the schema.
- *
- * @method setValidElements
- * @param {String} valid_elements String in the valid elements format to be parsed.
- */
- self.setValidElements = setValidElements;
- /**
- * Adds custom non HTML elements to the schema.
- *
- * @method addCustomElements
- * @param {String} custom_elements Comma separated list of custom elements to add.
- */
- self.addCustomElements = addCustomElements;
- /**
- * Parses a valid children string and adds them to the schema structure. The valid children
- * format is for example: "element[child1|child2]".
- *
- * @method addValidChildren
- * @param {String} valid_children Valid children elements string to parse
- */
- self.addValidChildren = addValidChildren;
- self.elements = elements;
- };
- });
- // Included from: js/tinymce/classes/html/SaxParser.js
- /**
- * SaxParser.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*eslint max-depth:[2, 9] */
- /**
- * This class parses HTML code using pure JavaScript and executes various events for each item it finds. It will
- * always execute the events in the right order for tag soup code like <b><p></b></p>. It will also remove elements
- * and attributes that doesn't fit the schema if the validate setting is enabled.
- *
- * @example
- * var parser = new tinymce.html.SaxParser({
- * validate: true,
- *
- * comment: function(text) {
- * console.log('Comment:', text);
- * },
- *
- * cdata: function(text) {
- * console.log('CDATA:', text);
- * },
- *
- * text: function(text, raw) {
- * console.log('Text:', text, 'Raw:', raw);
- * },
- *
- * start: function(name, attrs, empty) {
- * console.log('Start:', name, attrs, empty);
- * },
- *
- * end: function(name) {
- * console.log('End:', name);
- * },
- *
- * pi: function(name, text) {
- * console.log('PI:', name, text);
- * },
- *
- * doctype: function(text) {
- * console.log('DocType:', text);
- * }
- * }, schema);
- * @class tinymce.html.SaxParser
- * @version 3.4
- */
- define("tinymce/html/SaxParser", [
- "tinymce/html/Schema",
- "tinymce/html/Entities",
- "tinymce/util/Tools"
- ], function(Schema, Entities, Tools) {
- var each = Tools.each;
- /**
- * Returns the index of the end tag for a specific start tag. This can be
- * used to skip all children of a parent element from being processed.
- *
- * @private
- * @method findEndTag
- * @param {tinymce.html.Schema} schema Schema instance to use to match short ended elements.
- * @param {String} html HTML string to find the end tag in.
- * @param {Number} startIndex Indext to start searching at should be after the start tag.
- * @return {Number} Index of the end tag.
- */
- function findEndTag(schema, html, startIndex) {
- var count = 1, index, matches, tokenRegExp, shortEndedElements;
- shortEndedElements = schema.getShortEndedElements();
- tokenRegExp = /<([!?\/])?([A-Za-z0-9\-_\:\.]+)((?:\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\/|\s+)>/g;
- tokenRegExp.lastIndex = index = startIndex;
- while ((matches = tokenRegExp.exec(html))) {
- index = tokenRegExp.lastIndex;
- if (matches[1] === '/') { // End element
- count--;
- } else if (!matches[1]) { // Start element
- if (matches[2] in shortEndedElements) {
- continue;
- }
- count++;
- }
- if (count === 0) {
- break;
- }
- }
- return index;
- }
- /**
- * Constructs a new SaxParser instance.
- *
- * @constructor
- * @method SaxParser
- * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
- * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
- */
- function SaxParser(settings, schema) {
- var self = this;
- function noop() {}
- settings = settings || {};
- self.schema = schema = schema || new Schema();
- if (settings.fix_self_closing !== false) {
- settings.fix_self_closing = true;
- }
- // Add handler functions from settings and setup default handlers
- each('comment cdata text start end pi doctype'.split(' '), function(name) {
- if (name) {
- self[name] = settings[name] || noop;
- }
- });
- /**
- * Parses the specified HTML string and executes the callbacks for each item it finds.
- *
- * @example
- * new SaxParser({...}).parse('<b>text</b>');
- * @method parse
- * @param {String} html Html string to sax parse.
- */
- self.parse = function(html) {
- var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name;
- var isInternalElement, removeInternalElements, shortEndedElements, fillAttrsMap, isShortEnded;
- var validate, elementRule, isValidElement, attr, attribsValue, validAttributesMap, validAttributePatterns;
- var attributesRequired, attributesDefault, attributesForced;
- var anyAttributesRequired, selfClosing, tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0;
- var decode = Entities.decode, fixSelfClosing, filteredUrlAttrs = Tools.makeMap('src,href,data,background,formaction,poster');
- var scriptUriRegExp = /((java|vb)script|mhtml):/i, dataUriRegExp = /^data:/i;
- function processEndTag(name) {
- var pos, i;
- // Find position of parent of the same type
- pos = stack.length;
- while (pos--) {
- if (stack[pos].name === name) {
- break;
- }
- }
- // Found parent
- if (pos >= 0) {
- // Close all the open elements
- for (i = stack.length - 1; i >= pos; i--) {
- name = stack[i];
- if (name.valid) {
- self.end(name.name);
- }
- }
- // Remove the open elements from the stack
- stack.length = pos;
- }
- }
- function parseAttribute(match, name, value, val2, val3) {
- var attrRule, i, trimRegExp = /[\s\u0000-\u001F]+/g;
- name = name.toLowerCase();
- value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
- // Validate name and value pass through all data- attributes
- if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
- attrRule = validAttributesMap[name];
- // Find rule by pattern matching
- if (!attrRule && validAttributePatterns) {
- i = validAttributePatterns.length;
- while (i--) {
- attrRule = validAttributePatterns[i];
- if (attrRule.pattern.test(name)) {
- break;
- }
- }
- // No rule matched
- if (i === -1) {
- attrRule = null;
- }
- }
- // No attribute rule found
- if (!attrRule) {
- return;
- }
- // Validate value
- if (attrRule.validValues && !(value in attrRule.validValues)) {
- return;
- }
- }
- // Block any javascript: urls or non image data uris
- if (filteredUrlAttrs[name] && !settings.allow_script_urls) {
- var uri = value.replace(trimRegExp, '');
- try {
- // Might throw malformed URI sequence
- uri = decodeURIComponent(uri);
- } catch (ex) {
- // Fallback to non UTF-8 decoder
- uri = unescape(uri);
- }
- if (scriptUriRegExp.test(uri)) {
- return;
- }
- if (!settings.allow_html_data_urls && dataUriRegExp.test(uri) && !/^data:image\//i.test(uri)) {
- return;
- }
- }
- // Add attribute to list and map
- attrList.map[name] = value;
- attrList.push({
- name: name,
- value: value
- });
- }
- // Precompile RegExps and map objects
- tokenRegExp = new RegExp('<(?:' +
- '(?:!--([\\w\\W]*?)-->)|' + // Comment
- '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
- '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
- '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
- '(?:\\/([^>]+)>)|' + // End element
- '(?:([A-Za-z0-9\\-_\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
- ')', 'g');
- attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
- // Setup lookup tables for empty elements and boolean attributes
- shortEndedElements = schema.getShortEndedElements();
- selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
- fillAttrsMap = schema.getBoolAttrs();
- validate = settings.validate;
- removeInternalElements = settings.remove_internals;
- fixSelfClosing = settings.fix_self_closing;
- specialElements = schema.getSpecialElements();
- while ((matches = tokenRegExp.exec(html))) {
- // Text
- if (index < matches.index) {
- self.text(decode(html.substr(index, matches.index - index)));
- }
- if ((value = matches[6])) { // End element
- value = value.toLowerCase();
- // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
- if (value.charAt(0) === ':') {
- value = value.substr(1);
- }
- processEndTag(value);
- } else if ((value = matches[7])) { // Start element
- value = value.toLowerCase();
- // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
- if (value.charAt(0) === ':') {
- value = value.substr(1);
- }
- isShortEnded = value in shortEndedElements;
- // Is self closing tag for example an <li> after an open <li>
- if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) {
- processEndTag(value);
- }
- // Validate element
- if (!validate || (elementRule = schema.getElementRule(value))) {
- isValidElement = true;
- // Grab attributes map and patters when validation is enabled
- if (validate) {
- validAttributesMap = elementRule.attributes;
- validAttributePatterns = elementRule.attributePatterns;
- }
- // Parse attributes
- if ((attribsValue = matches[8])) {
- isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
- // If the element has internal attributes then remove it if we are told to do so
- if (isInternalElement && removeInternalElements) {
- isValidElement = false;
- }
- attrList = [];
- attrList.map = {};
- attribsValue.replace(attrRegExp, parseAttribute);
- } else {
- attrList = [];
- attrList.map = {};
- }
- // Process attributes if validation is enabled
- if (validate && !isInternalElement) {
- attributesRequired = elementRule.attributesRequired;
- attributesDefault = elementRule.attributesDefault;
- attributesForced = elementRule.attributesForced;
- anyAttributesRequired = elementRule.removeEmptyAttrs;
- // Check if any attribute exists
- if (anyAttributesRequired && !attrList.length) {
- isValidElement = false;
- }
- // Handle forced attributes
- if (attributesForced) {
- i = attributesForced.length;
- while (i--) {
- attr = attributesForced[i];
- name = attr.name;
- attrValue = attr.value;
- if (attrValue === '{$uid}') {
- attrValue = 'mce_' + idCount++;
- }
- attrList.map[name] = attrValue;
- attrList.push({name: name, value: attrValue});
- }
- }
- // Handle default attributes
- if (attributesDefault) {
- i = attributesDefault.length;
- while (i--) {
- attr = attributesDefault[i];
- name = attr.name;
- if (!(name in attrList.map)) {
- attrValue = attr.value;
- if (attrValue === '{$uid}') {
- attrValue = 'mce_' + idCount++;
- }
- attrList.map[name] = attrValue;
- attrList.push({name: name, value: attrValue});
- }
- }
- }
- // Handle required attributes
- if (attributesRequired) {
- i = attributesRequired.length;
- while (i--) {
- if (attributesRequired[i] in attrList.map) {
- break;
- }
- }
- // None of the required attributes where found
- if (i === -1) {
- isValidElement = false;
- }
- }
- // Invalidate element if it's marked as bogus
- if ((attr = attrList.map['data-mce-bogus'])) {
- if (attr === 'all') {
- index = findEndTag(schema, html, tokenRegExp.lastIndex);
- tokenRegExp.lastIndex = index;
- continue;
- }
- isValidElement = false;
- }
- }
- if (isValidElement) {
- self.start(value, attrList, isShortEnded);
- }
- } else {
- isValidElement = false;
- }
- // Treat script, noscript and style a bit different since they may include code that looks like elements
- if ((endRegExp = specialElements[value])) {
- endRegExp.lastIndex = index = matches.index + matches[0].length;
- if ((matches = endRegExp.exec(html))) {
- if (isValidElement) {
- text = html.substr(index, matches.index - index);
- }
- index = matches.index + matches[0].length;
- } else {
- text = html.substr(index);
- index = html.length;
- }
- if (isValidElement) {
- if (text.length > 0) {
- self.text(text, true);
- }
- self.end(value);
- }
- tokenRegExp.lastIndex = index;
- continue;
- }
- // Push value on to stack
- if (!isShortEnded) {
- if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) {
- stack.push({name: value, valid: isValidElement});
- } else if (isValidElement) {
- self.end(value);
- }
- }
- } else if ((value = matches[1])) { // Comment
- // Padd comment value to avoid browsers from parsing invalid comments as HTML
- if (value.charAt(0) === '>') {
- value = ' ' + value;
- }
- if (!settings.allow_conditional_comments && value.substr(0, 3) === '[if') {
- value = ' ' + value;
- }
- self.comment(value);
- } else if ((value = matches[2])) { // CDATA
- self.cdata(value);
- } else if ((value = matches[3])) { // DOCTYPE
- self.doctype(value);
- } else if ((value = matches[4])) { // PI
- self.pi(value, matches[5]);
- }
- index = matches.index + matches[0].length;
- }
- // Text
- if (index < html.length) {
- self.text(decode(html.substr(index)));
- }
- // Close any open elements
- for (i = stack.length - 1; i >= 0; i--) {
- value = stack[i];
- if (value.valid) {
- self.end(value.name);
- }
- }
- };
- }
- SaxParser.findEndTag = findEndTag;
- return SaxParser;
- });
- // Included from: js/tinymce/classes/html/DomParser.js
- /**
- * DomParser.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class parses HTML code into a DOM like structure of nodes it will remove redundant whitespace and make
- * sure that the node tree is valid according to the specified schema.
- * So for example: <p>a<p>b</p>c</p> will become <p>a</p><p>b</p><p>c</p>
- *
- * @example
- * var parser = new tinymce.html.DomParser({validate: true}, schema);
- * var rootNode = parser.parse('<h1>content</h1>');
- *
- * @class tinymce.html.DomParser
- * @version 3.4
- */
- define("tinymce/html/DomParser", [
- "tinymce/html/Node",
- "tinymce/html/Schema",
- "tinymce/html/SaxParser",
- "tinymce/util/Tools"
- ], function(Node, Schema, SaxParser, Tools) {
- var makeMap = Tools.makeMap, each = Tools.each, explode = Tools.explode, extend = Tools.extend;
- /**
- * Constructs a new DomParser instance.
- *
- * @constructor
- * @method DomParser
- * @param {Object} settings Name/value collection of settings. comment, cdata, text, start and end are callbacks.
- * @param {tinymce.html.Schema} schema HTML Schema class to use when parsing.
- */
- return function(settings, schema) {
- var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
- settings = settings || {};
- settings.validate = "validate" in settings ? settings.validate : true;
- settings.root_name = settings.root_name || 'body';
- self.schema = schema = schema || new Schema();
- function fixInvalidChildren(nodes) {
- var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i;
- var nonEmptyElements, nonSplitableElements, textBlockElements, specialElements, sibling, nextNode;
- nonSplitableElements = makeMap('tr,td,th,tbody,thead,tfoot,table');
- nonEmptyElements = schema.getNonEmptyElements();
- textBlockElements = schema.getTextBlockElements();
- specialElements = schema.getSpecialElements();
- for (ni = 0; ni < nodes.length; ni++) {
- node = nodes[ni];
- // Already removed or fixed
- if (!node.parent || node.fixed) {
- continue;
- }
- // If the invalid element is a text block and the text block is within a parent LI element
- // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
- if (textBlockElements[node.name] && node.parent.name == 'li') {
- // Move sibling text blocks after LI element
- sibling = node.next;
- while (sibling) {
- if (textBlockElements[sibling.name]) {
- sibling.name = 'li';
- sibling.fixed = true;
- node.parent.insert(sibling, node.parent);
- } else {
- break;
- }
- sibling = sibling.next;
- }
- // Unwrap current text block
- node.unwrap(node);
- continue;
- }
- // Get list of all parent nodes until we find a valid parent to stick the child into
- parents = [node];
- for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) &&
- !nonSplitableElements[parent.name]; parent = parent.parent) {
- parents.push(parent);
- }
- // Found a suitable parent
- if (parent && parents.length > 1) {
- // Reverse the array since it makes looping easier
- parents.reverse();
- // Clone the related parent and insert that after the moved node
- newParent = currentNode = self.filterNode(parents[0].clone());
- // Start cloning and moving children on the left side of the target node
- for (i = 0; i < parents.length - 1; i++) {
- if (schema.isValidChild(currentNode.name, parents[i].name)) {
- tempNode = self.filterNode(parents[i].clone());
- currentNode.append(tempNode);
- } else {
- tempNode = currentNode;
- }
- for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1];) {
- nextNode = childNode.next;
- tempNode.append(childNode);
- childNode = nextNode;
- }
- currentNode = tempNode;
- }
- if (!newParent.isEmpty(nonEmptyElements)) {
- parent.insert(newParent, parents[0], true);
- parent.insert(node, newParent);
- } else {
- parent.insert(node, parents[0], true);
- }
- // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
- parent = parents[0];
- if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
- parent.empty().remove();
- }
- } else if (node.parent) {
- // If it's an LI try to find a UL/OL for it or wrap it
- if (node.name === 'li') {
- sibling = node.prev;
- if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
- sibling.append(node);
- continue;
- }
- sibling = node.next;
- if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
- sibling.insert(node, sibling.firstChild, true);
- continue;
- }
- node.wrap(self.filterNode(new Node('ul', 1)));
- continue;
- }
- // Try wrapping the element in a DIV
- if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
- node.wrap(self.filterNode(new Node('div', 1)));
- } else {
- // We failed wrapping it, then remove or unwrap it
- if (specialElements[node.name]) {
- node.empty().remove();
- } else {
- node.unwrap();
- }
- }
- }
- }
- }
- /**
- * Runs the specified node though the element and attributes filters.
- *
- * @method filterNode
- * @param {tinymce.html.Node} Node the node to run filters on.
- * @return {tinymce.html.Node} The passed in node.
- */
- self.filterNode = function(node) {
- var i, name, list;
- // Run element filters
- if (name in nodeFilters) {
- list = matchedNodes[name];
- if (list) {
- list.push(node);
- } else {
- matchedNodes[name] = [node];
- }
- }
- // Run attribute filters
- i = attributeFilters.length;
- while (i--) {
- name = attributeFilters[i].name;
- if (name in node.attributes.map) {
- list = matchedAttributes[name];
- if (list) {
- list.push(node);
- } else {
- matchedAttributes[name] = [node];
- }
- }
- }
- return node;
- };
- /**
- * Adds a node filter function to the parser, the parser will collect the specified nodes by name
- * and then execute the callback ones it has finished parsing the document.
- *
- * @example
- * parser.addNodeFilter('p,h1', function(nodes, name) {
- * for (var i = 0; i < nodes.length; i++) {
- * console.log(nodes[i].name);
- * }
- * });
- * @method addNodeFilter
- * @method {String} name Comma separated list of nodes to collect.
- * @param {function} callback Callback function to execute once it has collected nodes.
- */
- self.addNodeFilter = function(name, callback) {
- each(explode(name), function(name) {
- var list = nodeFilters[name];
- if (!list) {
- nodeFilters[name] = list = [];
- }
- list.push(callback);
- });
- };
- /**
- * Adds a attribute filter function to the parser, the parser will collect nodes that has the specified attributes
- * and then execute the callback ones it has finished parsing the document.
- *
- * @example
- * parser.addAttributeFilter('src,href', function(nodes, name) {
- * for (var i = 0; i < nodes.length; i++) {
- * console.log(nodes[i].name);
- * }
- * });
- * @method addAttributeFilter
- * @method {String} name Comma separated list of nodes to collect.
- * @param {function} callback Callback function to execute once it has collected nodes.
- */
- self.addAttributeFilter = function(name, callback) {
- each(explode(name), function(name) {
- var i;
- for (i = 0; i < attributeFilters.length; i++) {
- if (attributeFilters[i].name === name) {
- attributeFilters[i].callbacks.push(callback);
- return;
- }
- }
- attributeFilters.push({name: name, callbacks: [callback]});
- });
- };
- /**
- * Parses the specified HTML string into a DOM like node tree and returns the result.
- *
- * @example
- * var rootNode = new DomParser({...}).parse('<b>text</b>');
- * @method parse
- * @param {String} html Html string to sax parse.
- * @param {Object} args Optional args object that gets passed to all filter functions.
- * @return {tinymce.html.Node} Root node containing the tree.
- */
- self.parse = function(html, args) {
- var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate;
- var blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement;
- var endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements;
- var children, nonEmptyElements, rootBlockName;
- args = args || {};
- matchedNodes = {};
- matchedAttributes = {};
- blockElements = extend(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
- nonEmptyElements = schema.getNonEmptyElements();
- children = schema.children;
- validate = settings.validate;
- rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
- whiteSpaceElements = schema.getWhiteSpaceElements();
- startWhiteSpaceRegExp = /^[ \t\r\n]+/;
- endWhiteSpaceRegExp = /[ \t\r\n]+$/;
- allWhiteSpaceRegExp = /[ \t\r\n]+/g;
- isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
- function addRootBlocks() {
- var node = rootNode.firstChild, next, rootBlockNode;
- // Removes whitespace at beginning and end of block so:
- // <p> x </p> -> <p>x</p>
- function trim(rootBlockNode) {
- if (rootBlockNode) {
- node = rootBlockNode.firstChild;
- if (node && node.type == 3) {
- node.value = node.value.replace(startWhiteSpaceRegExp, '');
- }
- node = rootBlockNode.lastChild;
- if (node && node.type == 3) {
- node.value = node.value.replace(endWhiteSpaceRegExp, '');
- }
- }
- }
- // Check if rootBlock is valid within rootNode for example if P is valid in H1 if H1 is the contentEditabe root
- if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
- return;
- }
- while (node) {
- next = node.next;
- if (node.type == 3 || (node.type == 1 && node.name !== 'p' &&
- !blockElements[node.name] && !node.attr('data-mce-type'))) {
- if (!rootBlockNode) {
- // Create a new root block element
- rootBlockNode = createNode(rootBlockName, 1);
- rootBlockNode.attr(settings.forced_root_block_attrs);
- rootNode.insert(rootBlockNode, node);
- rootBlockNode.append(node);
- } else {
- rootBlockNode.append(node);
- }
- } else {
- trim(rootBlockNode);
- rootBlockNode = null;
- }
- node = next;
- }
- trim(rootBlockNode);
- }
- function createNode(name, type) {
- var node = new Node(name, type), list;
- if (name in nodeFilters) {
- list = matchedNodes[name];
- if (list) {
- list.push(node);
- } else {
- matchedNodes[name] = [node];
- }
- }
- return node;
- }
- function removeWhitespaceBefore(node) {
- var textNode, textNodeNext, textVal, sibling, blockElements = schema.getBlockElements();
- for (textNode = node.prev; textNode && textNode.type === 3;) {
- textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
- // Found a text node with non whitespace then trim that and break
- if (textVal.length > 0) {
- textNode.value = textVal;
- return;
- }
- textNodeNext = textNode.next;
- // Fix for bug #7543 where bogus nodes would produce empty
- // text nodes and these would be removed if a nested list was before it
- if (textNodeNext) {
- if (textNodeNext.type == 3 && textNodeNext.value.length) {
- textNode = textNode.prev;
- continue;
- }
- if (!blockElements[textNodeNext.name] && textNodeNext.name != 'script' && textNodeNext.name != 'style') {
- textNode = textNode.prev;
- continue;
- }
- }
- sibling = textNode.prev;
- textNode.remove();
- textNode = sibling;
- }
- }
- function cloneAndExcludeBlocks(input) {
- var name, output = {};
- for (name in input) {
- if (name !== 'li' && name != 'p') {
- output[name] = input[name];
- }
- }
- return output;
- }
- parser = new SaxParser({
- validate: validate,
- allow_script_urls: settings.allow_script_urls,
- allow_conditional_comments: settings.allow_conditional_comments,
- // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
- self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
- cdata: function(text) {
- node.append(createNode('#cdata', 4)).value = text;
- },
- text: function(text, raw) {
- var textNode;
- // Trim all redundant whitespace on non white space elements
- if (!isInWhiteSpacePreservedElement) {
- text = text.replace(allWhiteSpaceRegExp, ' ');
- if (node.lastChild && blockElements[node.lastChild.name]) {
- text = text.replace(startWhiteSpaceRegExp, '');
- }
- }
- // Do we need to create the node
- if (text.length !== 0) {
- textNode = createNode('#text', 3);
- textNode.raw = !!raw;
- node.append(textNode).value = text;
- }
- },
- comment: function(text) {
- node.append(createNode('#comment', 8)).value = text;
- },
- pi: function(name, text) {
- node.append(createNode(name, 7)).value = text;
- removeWhitespaceBefore(node);
- },
- doctype: function(text) {
- var newNode;
- newNode = node.append(createNode('#doctype', 10));
- newNode.value = text;
- removeWhitespaceBefore(node);
- },
- start: function(name, attrs, empty) {
- var newNode, attrFiltersLen, elementRule, attrName, parent;
- elementRule = validate ? schema.getElementRule(name) : {};
- if (elementRule) {
- newNode = createNode(elementRule.outputName || name, 1);
- newNode.attributes = attrs;
- newNode.shortEnded = empty;
- node.append(newNode);
- // Check if node is valid child of the parent node is the child is
- // unknown we don't collect it since it's probably a custom element
- parent = children[node.name];
- if (parent && children[newNode.name] && !parent[newNode.name]) {
- invalidChildren.push(newNode);
- }
- attrFiltersLen = attributeFilters.length;
- while (attrFiltersLen--) {
- attrName = attributeFilters[attrFiltersLen].name;
- if (attrName in attrs.map) {
- list = matchedAttributes[attrName];
- if (list) {
- list.push(newNode);
- } else {
- matchedAttributes[attrName] = [newNode];
- }
- }
- }
- // Trim whitespace before block
- if (blockElements[name]) {
- removeWhitespaceBefore(newNode);
- }
- // Change current node if the element wasn't empty i.e not <br /> or <img />
- if (!empty) {
- node = newNode;
- }
- // Check if we are inside a whitespace preserved element
- if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
- isInWhiteSpacePreservedElement = true;
- }
- }
- },
- end: function(name) {
- var textNode, elementRule, text, sibling, tempNode;
- elementRule = validate ? schema.getElementRule(name) : {};
- if (elementRule) {
- if (blockElements[name]) {
- if (!isInWhiteSpacePreservedElement) {
- // Trim whitespace of the first node in a block
- textNode = node.firstChild;
- if (textNode && textNode.type === 3) {
- text = textNode.value.replace(startWhiteSpaceRegExp, '');
- // Any characters left after trim or should we remove it
- if (text.length > 0) {
- textNode.value = text;
- textNode = textNode.next;
- } else {
- sibling = textNode.next;
- textNode.remove();
- textNode = sibling;
- // Remove any pure whitespace siblings
- while (textNode && textNode.type === 3) {
- text = textNode.value;
- sibling = textNode.next;
- if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
- textNode.remove();
- textNode = sibling;
- }
- textNode = sibling;
- }
- }
- }
- // Trim whitespace of the last node in a block
- textNode = node.lastChild;
- if (textNode && textNode.type === 3) {
- text = textNode.value.replace(endWhiteSpaceRegExp, '');
- // Any characters left after trim or should we remove it
- if (text.length > 0) {
- textNode.value = text;
- textNode = textNode.prev;
- } else {
- sibling = textNode.prev;
- textNode.remove();
- textNode = sibling;
- // Remove any pure whitespace siblings
- while (textNode && textNode.type === 3) {
- text = textNode.value;
- sibling = textNode.prev;
- if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
- textNode.remove();
- textNode = sibling;
- }
- textNode = sibling;
- }
- }
- }
- }
- // Trim start white space
- // Removed due to: #5424
- /*textNode = node.prev;
- if (textNode && textNode.type === 3) {
- text = textNode.value.replace(startWhiteSpaceRegExp, '');
- if (text.length > 0)
- textNode.value = text;
- else
- textNode.remove();
- }*/
- }
- // Check if we exited a whitespace preserved element
- if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
- isInWhiteSpacePreservedElement = false;
- }
- // Handle empty nodes
- if (elementRule.removeEmpty || elementRule.paddEmpty) {
- if (node.isEmpty(nonEmptyElements)) {
- if (elementRule.paddEmpty) {
- node.empty().append(new Node('#text', '3')).value = '\u00a0';
- } else {
- // Leave nodes that have a name like <a name="name">
- if (!node.attributes.map.name && !node.attributes.map.id) {
- tempNode = node.parent;
- if (blockElements[node.name]) {
- node.empty().remove();
- } else {
- node.unwrap();
- }
- node = tempNode;
- return;
- }
- }
- }
- }
- node = node.parent;
- }
- }
- }, schema);
- rootNode = node = new Node(args.context || settings.root_name, 11);
- parser.parse(html);
- // Fix invalid children or report invalid children in a contextual parsing
- if (validate && invalidChildren.length) {
- if (!args.context) {
- fixInvalidChildren(invalidChildren);
- } else {
- args.invalid = true;
- }
- }
- // Wrap nodes in the root into block elements if the root is body
- if (rootBlockName && (rootNode.name == 'body' || args.isRootContent)) {
- addRootBlocks();
- }
- // Run filters only when the contents is valid
- if (!args.invalid) {
- // Run node filters
- for (name in matchedNodes) {
- list = nodeFilters[name];
- nodes = matchedNodes[name];
- // Remove already removed children
- fi = nodes.length;
- while (fi--) {
- if (!nodes[fi].parent) {
- nodes.splice(fi, 1);
- }
- }
- for (i = 0, l = list.length; i < l; i++) {
- list[i](nodes, name, args);
- }
- }
- // Run attribute filters
- for (i = 0, l = attributeFilters.length; i < l; i++) {
- list = attributeFilters[i];
- if (list.name in matchedAttributes) {
- nodes = matchedAttributes[list.name];
- // Remove already removed children
- fi = nodes.length;
- while (fi--) {
- if (!nodes[fi].parent) {
- nodes.splice(fi, 1);
- }
- }
- for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) {
- list.callbacks[fi](nodes, list.name, args);
- }
- }
- }
- }
- return rootNode;
- };
- // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
- // make it possible to place the caret inside empty blocks. This logic tries to remove
- // these elements and keep br elements that where intended to be there intact
- if (settings.remove_trailing_brs) {
- self.addNodeFilter('br', function(nodes) {
- var i, l = nodes.length, node, blockElements = extend({}, schema.getBlockElements());
- var nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
- var elementRule, textNode;
- // Remove brs from body element as well
- blockElements.body = 1;
- // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
- for (i = 0; i < l; i++) {
- node = nodes[i];
- parent = node.parent;
- if (blockElements[node.parent.name] && node === parent.lastChild) {
- // Loop all nodes to the left of the current node and check for other BR elements
- // excluding bookmarks since they are invisible
- prev = node.prev;
- while (prev) {
- prevName = prev.name;
- // Ignore bookmarks
- if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
- // Found a non BR element
- if (prevName !== "br") {
- break;
- }
- // Found another br it's a <br><br> structure then don't remove anything
- if (prevName === 'br') {
- node = null;
- break;
- }
- }
- prev = prev.prev;
- }
- if (node) {
- node.remove();
- // Is the parent to be considered empty after we removed the BR
- if (parent.isEmpty(nonEmptyElements)) {
- elementRule = schema.getElementRule(parent.name);
- // Remove or padd the element depending on schema rule
- if (elementRule) {
- if (elementRule.removeEmpty) {
- parent.remove();
- } else if (elementRule.paddEmpty) {
- parent.empty().append(new Node('#text', 3)).value = '\u00a0';
- }
- }
- }
- }
- } else {
- // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p>
- // so they become <p><b><i> </i></b></p>
- lastParent = node;
- while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
- lastParent = parent;
- if (blockElements[parent.name]) {
- break;
- }
- parent = parent.parent;
- }
- if (lastParent === parent) {
- textNode = new Node('#text', 3);
- textNode.value = '\u00a0';
- node.replace(textNode);
- }
- }
- }
- });
- }
- // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
- if (!settings.allow_html_in_named_anchor) {
- self.addAttributeFilter('id,name', function(nodes) {
- var i = nodes.length, sibling, prevSibling, parent, node;
- while (i--) {
- node = nodes[i];
- if (node.name === 'a' && node.firstChild && !node.attr('href')) {
- parent = node.parent;
- // Move children after current node
- sibling = node.lastChild;
- do {
- prevSibling = sibling.prev;
- parent.insert(sibling, node);
- sibling = prevSibling;
- } while (sibling);
- }
- }
- });
- }
- if (settings.validate && schema.getValidClasses()) {
- self.addAttributeFilter('class', function(nodes) {
- var i = nodes.length, node, classList, ci, className, classValue;
- var validClasses = schema.getValidClasses(), validClassesMap, valid;
- while (i--) {
- node = nodes[i];
- classList = node.attr('class').split(' ');
- classValue = '';
- for (ci = 0; ci < classList.length; ci++) {
- className = classList[ci];
- valid = false;
- validClassesMap = validClasses['*'];
- if (validClassesMap && validClassesMap[className]) {
- valid = true;
- }
- validClassesMap = validClasses[node.name];
- if (!valid && validClassesMap && validClassesMap[className]) {
- valid = true;
- }
- if (valid) {
- if (classValue) {
- classValue += ' ';
- }
- classValue += className;
- }
- }
- if (!classValue.length) {
- classValue = null;
- }
- node.attr('class', classValue);
- }
- });
- }
- };
- });
- // Included from: js/tinymce/classes/html/Writer.js
- /**
- * Writer.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is used to write HTML tags out it can be used with the Serializer or the SaxParser.
- *
- * @class tinymce.html.Writer
- * @example
- * var writer = new tinymce.html.Writer({indent: true});
- * var parser = new tinymce.html.SaxParser(writer).parse('<p><br></p>');
- * console.log(writer.getContent());
- *
- * @class tinymce.html.Writer
- * @version 3.4
- */
- define("tinymce/html/Writer", [
- "tinymce/html/Entities",
- "tinymce/util/Tools"
- ], function(Entities, Tools) {
- var makeMap = Tools.makeMap;
- /**
- * Constructs a new Writer instance.
- *
- * @constructor
- * @method Writer
- * @param {Object} settings Name/value settings object.
- */
- return function(settings) {
- var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
- settings = settings || {};
- indent = settings.indent;
- indentBefore = makeMap(settings.indent_before || '');
- indentAfter = makeMap(settings.indent_after || '');
- encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
- htmlOutput = settings.element_format == "html";
- return {
- /**
- * Writes the a start element such as <p id="a">.
- *
- * @method start
- * @param {String} name Name of the element.
- * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
- * @param {Boolean} empty Optional empty state if the tag should end like <br />.
- */
- start: function(name, attrs, empty) {
- var i, l, attr, value;
- if (indent && indentBefore[name] && html.length > 0) {
- value = html[html.length - 1];
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
- html.push('<', name);
- if (attrs) {
- for (i = 0, l = attrs.length; i < l; i++) {
- attr = attrs[i];
- html.push(' ', attr.name, '="', encode(attr.value, true), '"');
- }
- }
- if (!empty || htmlOutput) {
- html[html.length] = '>';
- } else {
- html[html.length] = ' />';
- }
- if (empty && indent && indentAfter[name] && html.length > 0) {
- value = html[html.length - 1];
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
- },
- /**
- * Writes the a end element such as </p>.
- *
- * @method end
- * @param {String} name Name of the element.
- */
- end: function(name) {
- var value;
- /*if (indent && indentBefore[name] && html.length > 0) {
- value = html[html.length - 1];
- if (value.length > 0 && value !== '\n')
- html.push('\n');
- }*/
- html.push('</', name, '>');
- if (indent && indentAfter[name] && html.length > 0) {
- value = html[html.length - 1];
- if (value.length > 0 && value !== '\n') {
- html.push('\n');
- }
- }
- },
- /**
- * Writes a text node.
- *
- * @method text
- * @param {String} text String to write out.
- * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
- */
- text: function(text, raw) {
- if (text.length > 0) {
- html[html.length] = raw ? text : encode(text);
- }
- },
- /**
- * Writes a cdata node such as <![CDATA[data]]>.
- *
- * @method cdata
- * @param {String} text String to write out inside the cdata.
- */
- cdata: function(text) {
- html.push('<![CDATA[', text, ']]>');
- },
- /**
- * Writes a comment node such as <!-- Comment -->.
- *
- * @method cdata
- * @param {String} text String to write out inside the comment.
- */
- comment: function(text) {
- html.push('<!--', text, '-->');
- },
- /**
- * Writes a PI node such as <?xml attr="value" ?>.
- *
- * @method pi
- * @param {String} name Name of the pi.
- * @param {String} text String to write out inside the pi.
- */
- pi: function(name, text) {
- if (text) {
- html.push('<?', name, ' ', encode(text), '?>');
- } else {
- html.push('<?', name, '?>');
- }
- if (indent) {
- html.push('\n');
- }
- },
- /**
- * Writes a doctype node such as <!DOCTYPE data>.
- *
- * @method doctype
- * @param {String} text String to write out inside the doctype.
- */
- doctype: function(text) {
- html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
- },
- /**
- * Resets the internal buffer if one wants to reuse the writer.
- *
- * @method reset
- */
- reset: function() {
- html.length = 0;
- },
- /**
- * Returns the contents that got serialized.
- *
- * @method getContent
- * @return {String} HTML contents that got written down.
- */
- getContent: function() {
- return html.join('').replace(/\n$/, '');
- }
- };
- };
- });
- // Included from: js/tinymce/classes/html/Serializer.js
- /**
- * Serializer.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is used to serialize down the DOM tree into a string using a Writer instance.
- *
- *
- * @example
- * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
- * @class tinymce.html.Serializer
- * @version 3.4
- */
- define("tinymce/html/Serializer", [
- "tinymce/html/Writer",
- "tinymce/html/Schema"
- ], function(Writer, Schema) {
- /**
- * Constructs a new Serializer instance.
- *
- * @constructor
- * @method Serializer
- * @param {Object} settings Name/value settings object.
- * @param {tinymce.html.Schema} schema Schema instance to use.
- */
- return function(settings, schema) {
- var self = this, writer = new Writer(settings);
- settings = settings || {};
- settings.validate = "validate" in settings ? settings.validate : true;
- self.schema = schema = schema || new Schema();
- self.writer = writer;
- /**
- * Serializes the specified node into a string.
- *
- * @example
- * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
- * @method serialize
- * @param {tinymce.html.Node} node Node instance to serialize.
- * @return {String} String with HTML based on DOM tree.
- */
- self.serialize = function(node) {
- var handlers, validate;
- validate = settings.validate;
- handlers = {
- // #text
- 3: function(node) {
- writer.text(node.value, node.raw);
- },
- // #comment
- 8: function(node) {
- writer.comment(node.value);
- },
- // Processing instruction
- 7: function(node) {
- writer.pi(node.name, node.value);
- },
- // Doctype
- 10: function(node) {
- writer.doctype(node.value);
- },
- // CDATA
- 4: function(node) {
- writer.cdata(node.value);
- },
- // Document fragment
- 11: function(node) {
- if ((node = node.firstChild)) {
- do {
- walk(node);
- } while ((node = node.next));
- }
- }
- };
- writer.reset();
- function walk(node) {
- var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
- if (!handler) {
- name = node.name;
- isEmpty = node.shortEnded;
- attrs = node.attributes;
- // Sort attributes
- if (validate && attrs && attrs.length > 1) {
- sortedAttrs = [];
- sortedAttrs.map = {};
- elementRule = schema.getElementRule(node.name);
- if (elementRule) {
- for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
- attrName = elementRule.attributesOrder[i];
- if (attrName in attrs.map) {
- attrValue = attrs.map[attrName];
- sortedAttrs.map[attrName] = attrValue;
- sortedAttrs.push({name: attrName, value: attrValue});
- }
- }
- for (i = 0, l = attrs.length; i < l; i++) {
- attrName = attrs[i].name;
- if (!(attrName in sortedAttrs.map)) {
- attrValue = attrs.map[attrName];
- sortedAttrs.map[attrName] = attrValue;
- sortedAttrs.push({name: attrName, value: attrValue});
- }
- }
- attrs = sortedAttrs;
- }
- }
- writer.start(node.name, attrs, isEmpty);
- if (!isEmpty) {
- if ((node = node.firstChild)) {
- do {
- walk(node);
- } while ((node = node.next));
- }
- writer.end(name);
- }
- } else {
- handler(node);
- }
- }
- // Serialize element and treat all non elements as fragments
- if (node.type == 1 && !settings.inner) {
- walk(node);
- } else {
- handlers[11](node);
- }
- return writer.getContent();
- };
- };
- });
- // Included from: js/tinymce/classes/dom/Serializer.js
- /**
- * Serializer.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is used to serialize DOM trees into a string. Consult the TinyMCE Wiki API for
- * more details and examples on how to use this class.
- *
- * @class tinymce.dom.Serializer
- */
- define("tinymce/dom/Serializer", [
- "tinymce/dom/DOMUtils",
- "tinymce/html/DomParser",
- "tinymce/html/SaxParser",
- "tinymce/html/Entities",
- "tinymce/html/Serializer",
- "tinymce/html/Node",
- "tinymce/html/Schema",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/text/Zwsp"
- ], function(DOMUtils, DomParser, SaxParser, Entities, Serializer, Node, Schema, Env, Tools, Zwsp) {
- var each = Tools.each, trim = Tools.trim;
- var DOM = DOMUtils.DOM, tempAttrs = ["data-mce-selected"];
- /**
- * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
- * the iframe is hidden by display: none on a parent container. The DOM is actually out of sync
- * with innerHTML in this case. It's like IE adds shadow DOM BR elements that appears on innerHTML
- * but not as the lastChild of the body. So this fix simply removes the last two
- * BR elements at the end of the document.
- *
- * Example of what happens: <body>text</body> becomes <body>text<br><br></body>
- */
- function trimTrailingBr(rootNode) {
- var brNode1, brNode2;
- function isBr(node) {
- return node && node.name === 'br';
- }
- brNode1 = rootNode.lastChild;
- if (isBr(brNode1)) {
- brNode2 = brNode1.prev;
- if (isBr(brNode2)) {
- brNode1.remove();
- brNode2.remove();
- }
- }
- }
- /**
- * Constructs a new DOM serializer class.
- *
- * @constructor
- * @method Serializer
- * @param {Object} settings Serializer settings object.
- * @param {tinymce.Editor} editor Optional editor to bind events to and get schema/dom from.
- */
- return function(settings, editor) {
- var dom, schema, htmlParser;
- if (editor) {
- dom = editor.dom;
- schema = editor.schema;
- }
- function trimHtml(html) {
- var trimContentRegExp = new RegExp([
- '<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\\/span>', // Trim bogus spans like caret containers
- '\\s?(' + tempAttrs.join('|') + ')="[^"]+"' // Trim temporaty data-mce prefixed attributes like data-mce-selected
- ].join('|'), 'gi');
- html = Zwsp.trim(html.replace(trimContentRegExp, ''));
- return html;
- }
- /**
- * Returns a trimmed version of the editor contents to be used for the undo level. This
- * will remove any data-mce-bogus="all" marked elements since these are used for UI it will also
- * remove the data-mce-selected attributes used for selection of objects and caret containers.
- * It will keep all data-mce-bogus="1" elements since these can be used to place the caret etc and will
- * be removed by the serialization logic when you save.
- *
- * @private
- * @return {String} HTML contents of the editor excluding some internal bogus elements.
- */
- function getTrimmedContent() {
- var content = editor.getBody().innerHTML;
- var bogusAllRegExp = /<(\w+) [^>]*data-mce-bogus="all"[^>]*>/g;
- var endTagIndex, index, matchLength, matches, shortEndedElements, schema = editor.schema;
- content = trimHtml(content);
- shortEndedElements = schema.getShortEndedElements();
- // Remove all bogus elements marked with "all"
- while ((matches = bogusAllRegExp.exec(content))) {
- index = bogusAllRegExp.lastIndex;
- matchLength = matches[0].length;
- if (shortEndedElements[matches[1]]) {
- endTagIndex = index;
- } else {
- endTagIndex = SaxParser.findEndTag(schema, content, index);
- }
- content = content.substring(0, index - matchLength) + content.substring(endTagIndex);
- bogusAllRegExp.lastIndex = index - matchLength;
- }
- return trim(content);
- }
- function addTempAttr(name) {
- if (Tools.inArray(tempAttrs, name) === -1) {
- htmlParser.addAttributeFilter(name, function(nodes, name) {
- var i = nodes.length;
- while (i--) {
- nodes[i].attr(name, null);
- }
- });
- tempAttrs.push(name);
- }
- }
- // Default DOM and Schema if they are undefined
- dom = dom || DOM;
- schema = schema || new Schema(settings);
- settings.entity_encoding = settings.entity_encoding || 'named';
- settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
- htmlParser = new DomParser(settings, schema);
- // Convert tabindex back to elements when serializing contents
- htmlParser.addAttributeFilter('data-mce-tabindex', function(nodes, name) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- node.attr('tabindex', node.attributes.map['data-mce-tabindex']);
- node.attr(name, null);
- }
- });
- // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
- htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
- var i = nodes.length, node, value, internalName = 'data-mce-' + name;
- var urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
- while (i--) {
- node = nodes[i];
- value = node.attributes.map[internalName];
- if (value !== undef) {
- // Set external name to internal value and remove internal
- node.attr(name, value.length > 0 ? value : null);
- node.attr(internalName, null);
- } else {
- // No internal attribute found then convert the value we have in the DOM
- value = node.attributes.map[name];
- if (name === "style") {
- value = dom.serializeStyle(dom.parseStyle(value), node.name);
- } else if (urlConverter) {
- value = urlConverter.call(urlConverterScope, value, name, node.name);
- }
- node.attr(name, value.length > 0 ? value : null);
- }
- }
- });
- // Remove internal classes mceItem<..> or mceSelected
- htmlParser.addAttributeFilter('class', function(nodes) {
- var i = nodes.length, node, value;
- while (i--) {
- node = nodes[i];
- value = node.attr('class');
- if (value) {
- value = node.attr('class').replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
- node.attr('class', value.length > 0 ? value : null);
- }
- }
- });
- // Remove bookmark elements
- htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) {
- node.remove();
- }
- }
- });
- htmlParser.addNodeFilter('noscript', function(nodes) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i].firstChild;
- if (node) {
- node.value = Entities.decode(node.value);
- }
- }
- });
- // Force script into CDATA sections and remove the mce- prefix also add comments around styles
- htmlParser.addNodeFilter('script,style', function(nodes, name) {
- var i = nodes.length, node, value, type;
- function trim(value) {
- /*jshint maxlen:255 */
- /*eslint max-len:0 */
- return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
- .replace(/^[\r\n]*|[\r\n]*$/g, '')
- .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
- .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
- }
- while (i--) {
- node = nodes[i];
- value = node.firstChild ? node.firstChild.value : '';
- if (name === "script") {
- // Remove mce- prefix from script elements and remove default type since the user specified
- // a script element without type attribute
- type = node.attr('type');
- if (type) {
- node.attr('type', type == 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
- }
- if (value.length > 0) {
- node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
- }
- } else {
- if (value.length > 0) {
- node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
- }
- }
- }
- });
- // Convert comments to cdata and handle protected comments
- htmlParser.addNodeFilter('#comment', function(nodes) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- if (node.value.indexOf('[CDATA[') === 0) {
- node.name = '#cdata';
- node.type = 4;
- node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
- } else if (node.value.indexOf('mce:protected ') === 0) {
- node.name = "#text";
- node.type = 3;
- node.raw = true;
- node.value = unescape(node.value).substr(14);
- }
- }
- });
- htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- if (node.type === 7) {
- node.remove();
- } else if (node.type === 1) {
- if (name === "input" && !("type" in node.attributes.map)) {
- node.attr('type', 'text');
- }
- }
- }
- });
- // Fix list elements, TODO: Replace this later
- if (settings.fix_list_elements) {
- htmlParser.addNodeFilter('ul,ol', function(nodes) {
- var i = nodes.length, node, parentNode;
- while (i--) {
- node = nodes[i];
- parentNode = node.parent;
- if (parentNode.name === 'ul' || parentNode.name === 'ol') {
- if (node.prev && node.prev.name === 'li') {
- node.prev.append(node);
- }
- }
- }
- });
- }
- // Remove internal data attributes
- htmlParser.addAttributeFilter(
- 'data-mce-src,data-mce-href,data-mce-style,' +
- 'data-mce-selected,data-mce-expando,' +
- 'data-mce-type,data-mce-resize',
- function(nodes, name) {
- var i = nodes.length;
- while (i--) {
- nodes[i].attr(name, null);
- }
- }
- );
- // Return public methods
- return {
- /**
- * Schema instance that was used to when the Serializer was constructed.
- *
- * @field {tinymce.html.Schema} schema
- */
- schema: schema,
- /**
- * Adds a node filter function to the parser used by the serializer, the parser will collect the specified nodes by name
- * and then execute the callback ones it has finished parsing the document.
- *
- * @example
- * parser.addNodeFilter('p,h1', function(nodes, name) {
- * for (var i = 0; i < nodes.length; i++) {
- * console.log(nodes[i].name);
- * }
- * });
- * @method addNodeFilter
- * @method {String} name Comma separated list of nodes to collect.
- * @param {function} callback Callback function to execute once it has collected nodes.
- */
- addNodeFilter: htmlParser.addNodeFilter,
- /**
- * Adds a attribute filter function to the parser used by the serializer, the parser will
- * collect nodes that has the specified attributes
- * and then execute the callback ones it has finished parsing the document.
- *
- * @example
- * parser.addAttributeFilter('src,href', function(nodes, name) {
- * for (var i = 0; i < nodes.length; i++) {
- * console.log(nodes[i].name);
- * }
- * });
- * @method addAttributeFilter
- * @method {String} name Comma separated list of nodes to collect.
- * @param {function} callback Callback function to execute once it has collected nodes.
- */
- addAttributeFilter: htmlParser.addAttributeFilter,
- /**
- * Serializes the specified browser DOM node into a HTML string.
- *
- * @method serialize
- * @param {DOMNode} node DOM node to serialize.
- * @param {Object} args Arguments option that gets passed to event handlers.
- */
- serialize: function(node, args) {
- var self = this, impl, doc, oldDoc, htmlSerializer, content, rootNode;
- // Explorer won't clone contents of script and style and the
- // selected index of select elements are cleared on a clone operation.
- if (Env.ie && dom.select('script,style,select,map').length > 0) {
- content = node.innerHTML;
- node = node.cloneNode(false);
- dom.setHTML(node, content);
- } else {
- node = node.cloneNode(true);
- }
- // Nodes needs to be attached to something in WebKit/Opera
- // This fix will make DOM ranges and make Sizzle happy!
- impl = node.ownerDocument.implementation;
- if (impl.createHTMLDocument) {
- // Create an empty HTML document
- doc = impl.createHTMLDocument("");
- // Add the element or it's children if it's a body element to the new document
- each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
- doc.body.appendChild(doc.importNode(node, true));
- });
- // Grab first child or body element for serialization
- if (node.nodeName != 'BODY') {
- node = doc.body.firstChild;
- } else {
- node = doc.body;
- }
- // set the new document in DOMUtils so createElement etc works
- oldDoc = dom.doc;
- dom.doc = doc;
- }
- args = args || {};
- args.format = args.format || 'html';
- // Don't wrap content if we want selected html
- if (args.selection) {
- args.forced_root_block = '';
- }
- // Pre process
- if (!args.no_events) {
- args.node = node;
- self.onPreProcess(args);
- }
- // Parse HTML
- rootNode = htmlParser.parse(trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args);
- trimTrailingBr(rootNode);
- // Serialize HTML
- htmlSerializer = new Serializer(settings, schema);
- args.content = htmlSerializer.serialize(rootNode);
- // Replace all BOM characters for now until we can find a better solution
- if (!args.cleanup) {
- args.content = Zwsp.trim(args.content);
- args.content = args.content.replace(/\uFEFF/g, '');
- }
- // Post process
- if (!args.no_events) {
- self.onPostProcess(args);
- }
- // Restore the old document if it was changed
- if (oldDoc) {
- dom.doc = oldDoc;
- }
- args.node = null;
- return args.content;
- },
- /**
- * Adds valid elements rules to the serializers schema instance this enables you to specify things
- * like what elements should be outputted and what attributes specific elements might have.
- * Consult the Wiki for more details on this format.
- *
- * @method addRules
- * @param {String} rules Valid elements rules string to add to schema.
- */
- addRules: function(rules) {
- schema.addValidElements(rules);
- },
- /**
- * Sets the valid elements rules to the serializers schema instance this enables you to specify things
- * like what elements should be outputted and what attributes specific elements might have.
- * Consult the Wiki for more details on this format.
- *
- * @method setRules
- * @param {String} rules Valid elements rules string.
- */
- setRules: function(rules) {
- schema.setValidElements(rules);
- },
- onPreProcess: function(args) {
- if (editor) {
- editor.fire('PreProcess', args);
- }
- },
- onPostProcess: function(args) {
- if (editor) {
- editor.fire('PostProcess', args);
- }
- },
- /**
- * Adds a temporary internal attribute these attributes will get removed on undo and
- * when getting contents out of the editor.
- *
- * @method addTempAttr
- * @param {String} name string
- */
- addTempAttr: addTempAttr,
- // Internal
- trimHtml: trimHtml,
- getTrimmedContent: getTrimmedContent
- };
- };
- });
- // Included from: js/tinymce/classes/dom/TridentSelection.js
- /**
- * TridentSelection.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Selection class for old explorer versions. This one fakes the
- * native selection object available on modern browsers.
- *
- * @private
- * @class tinymce.dom.TridentSelection
- */
- define("tinymce/dom/TridentSelection", [], function() {
- function Selection(selection) {
- var self = this, dom = selection.dom, FALSE = false;
- function getPosition(rng, start) {
- var checkRng, startIndex = 0, endIndex, inside,
- children, child, offset, index, position = -1, parent;
- // Setup test range, collapse it and get the parent
- checkRng = rng.duplicate();
- checkRng.collapse(start);
- parent = checkRng.parentElement();
- // Check if the selection is within the right document
- if (parent.ownerDocument !== selection.dom.doc) {
- return;
- }
- // IE will report non editable elements as it's parent so look for an editable one
- while (parent.contentEditable === "false") {
- parent = parent.parentNode;
- }
- // If parent doesn't have any children then return that we are inside the element
- if (!parent.hasChildNodes()) {
- return {node: parent, inside: 1};
- }
- // Setup node list and endIndex
- children = parent.children;
- endIndex = children.length - 1;
- // Perform a binary search for the position
- while (startIndex <= endIndex) {
- index = Math.floor((startIndex + endIndex) / 2);
- // Move selection to node and compare the ranges
- child = children[index];
- checkRng.moveToElementText(child);
- position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
- // Before/after or an exact match
- if (position > 0) {
- endIndex = index - 1;
- } else if (position < 0) {
- startIndex = index + 1;
- } else {
- return {node: child};
- }
- }
- // Check if child position is before or we didn't find a position
- if (position < 0) {
- // No element child was found use the parent element and the offset inside that
- if (!child) {
- checkRng.moveToElementText(parent);
- checkRng.collapse(true);
- child = parent;
- inside = true;
- } else {
- checkRng.collapse(false);
- }
- // Walk character by character in text node until we hit the selected range endpoint,
- // hit the end of document or parent isn't the right one
- // We need to walk char by char since rng.text or rng.htmlText will trim line endings
- offset = 0;
- while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
- if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
- break;
- }
- offset++;
- }
- } else {
- // Child position is after the selection endpoint
- checkRng.collapse(true);
- // Walk character by character in text node until we hit the selected range endpoint, hit
- // the end of document or parent isn't the right one
- offset = 0;
- while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
- if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
- break;
- }
- offset++;
- }
- }
- return {node: child, position: position, offset: offset, inside: inside};
- }
- // Returns a W3C DOM compatible range object by using the IE Range API
- function getRange() {
- var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark;
- // If selection is outside the current document just return an empty range
- element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
- if (element.ownerDocument != dom.doc) {
- return domRange;
- }
- collapsed = selection.isCollapsed();
- // Handle control selection
- if (ieRange.item) {
- domRange.setStart(element.parentNode, dom.nodeIndex(element));
- domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
- return domRange;
- }
- function findEndPoint(start) {
- var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
- container = endPoint.node;
- offset = endPoint.offset;
- if (endPoint.inside && !container.hasChildNodes()) {
- domRange[start ? 'setStart' : 'setEnd'](container, 0);
- return;
- }
- if (offset === undef) {
- domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
- return;
- }
- if (endPoint.position < 0) {
- sibling = endPoint.inside ? container.firstChild : container.nextSibling;
- if (!sibling) {
- domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
- return;
- }
- if (!offset) {
- if (sibling.nodeType == 3) {
- domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
- } else {
- domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
- }
- return;
- }
- // Find the text node and offset
- while (sibling) {
- if (sibling.nodeType == 3) {
- nodeValue = sibling.nodeValue;
- textNodeOffset += nodeValue.length;
- // We are at or passed the position we where looking for
- if (textNodeOffset >= offset) {
- container = sibling;
- textNodeOffset -= offset;
- textNodeOffset = nodeValue.length - textNodeOffset;
- break;
- }
- }
- sibling = sibling.nextSibling;
- }
- } else {
- // Find the text node and offset
- sibling = container.previousSibling;
- if (!sibling) {
- return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
- }
- // If there isn't any text to loop then use the first position
- if (!offset) {
- if (container.nodeType == 3) {
- domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
- } else {
- domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
- }
- return;
- }
- while (sibling) {
- if (sibling.nodeType == 3) {
- textNodeOffset += sibling.nodeValue.length;
- // We are at or passed the position we where looking for
- if (textNodeOffset >= offset) {
- container = sibling;
- textNodeOffset -= offset;
- break;
- }
- }
- sibling = sibling.previousSibling;
- }
- }
- domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
- }
- try {
- // Find start point
- findEndPoint(true);
- // Find end point if needed
- if (!collapsed) {
- findEndPoint();
- }
- } catch (ex) {
- // IE has a nasty bug where text nodes might throw "invalid argument" when you
- // access the nodeValue or other properties of text nodes. This seems to happen when
- // text nodes are split into two nodes by a delete/backspace call.
- // So let us detect and try to fix it.
- if (ex.number == -2147024809) {
- // Get the current selection
- bookmark = self.getBookmark(2);
- // Get start element
- tmpRange = ieRange.duplicate();
- tmpRange.collapse(true);
- element = tmpRange.parentElement();
- // Get end element
- if (!collapsed) {
- tmpRange = ieRange.duplicate();
- tmpRange.collapse(false);
- element2 = tmpRange.parentElement();
- element2.innerHTML = element2.innerHTML;
- }
- // Remove the broken elements
- element.innerHTML = element.innerHTML;
- // Restore the selection
- self.moveToBookmark(bookmark);
- // Since the range has moved we need to re-get it
- ieRange = selection.getRng();
- // Find start point
- findEndPoint(true);
- // Find end point if needed
- if (!collapsed) {
- findEndPoint();
- }
- } else {
- throw ex; // Throw other errors
- }
- }
- return domRange;
- }
- this.getBookmark = function(type) {
- var rng = selection.getRng(), bookmark = {};
- function getIndexes(node) {
- var parent, root, children, i, indexes = [];
- parent = node.parentNode;
- root = dom.getRoot().parentNode;
- while (parent != root && parent.nodeType !== 9) {
- children = parent.children;
- i = children.length;
- while (i--) {
- if (node === children[i]) {
- indexes.push(i);
- break;
- }
- }
- node = parent;
- parent = parent.parentNode;
- }
- return indexes;
- }
- function getBookmarkEndPoint(start) {
- var position;
- position = getPosition(rng, start);
- if (position) {
- return {
- position: position.position,
- offset: position.offset,
- indexes: getIndexes(position.node),
- inside: position.inside
- };
- }
- }
- // Non ubstructive bookmark
- if (type === 2) {
- // Handle text selection
- if (!rng.item) {
- bookmark.start = getBookmarkEndPoint(true);
- if (!selection.isCollapsed()) {
- bookmark.end = getBookmarkEndPoint();
- }
- } else {
- bookmark.start = {ctrl: true, indexes: getIndexes(rng.item(0))};
- }
- }
- return bookmark;
- };
- this.moveToBookmark = function(bookmark) {
- var rng, body = dom.doc.body;
- function resolveIndexes(indexes) {
- var node, i, idx, children;
- node = dom.getRoot();
- for (i = indexes.length - 1; i >= 0; i--) {
- children = node.children;
- idx = indexes[i];
- if (idx <= children.length - 1) {
- node = children[idx];
- }
- }
- return node;
- }
- function setBookmarkEndPoint(start) {
- var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef, offset;
- if (endPoint) {
- moveLeft = endPoint.position > 0;
- moveRng = body.createTextRange();
- moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
- offset = endPoint.offset;
- if (offset !== undef) {
- moveRng.collapse(endPoint.inside || moveLeft);
- moveRng.moveStart('character', moveLeft ? -offset : offset);
- } else {
- moveRng.collapse(start);
- }
- rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
- if (start) {
- rng.collapse(true);
- }
- }
- }
- if (bookmark.start) {
- if (bookmark.start.ctrl) {
- rng = body.createControlRange();
- rng.addElement(resolveIndexes(bookmark.start.indexes));
- rng.select();
- } else {
- rng = body.createTextRange();
- setBookmarkEndPoint(true);
- setBookmarkEndPoint();
- rng.select();
- }
- }
- };
- this.addRange = function(rng) {
- var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
- doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
- function setEndPoint(start) {
- var container, offset, marker, tmpRng, nodes;
- marker = dom.create('a');
- container = start ? startContainer : endContainer;
- offset = start ? startOffset : endOffset;
- tmpRng = ieRng.duplicate();
- if (container == doc || container == doc.documentElement) {
- container = body;
- offset = 0;
- }
- if (container.nodeType == 3) {
- container.parentNode.insertBefore(marker, container);
- tmpRng.moveToElementText(marker);
- tmpRng.moveStart('character', offset);
- dom.remove(marker);
- ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
- } else {
- nodes = container.childNodes;
- if (nodes.length) {
- if (offset >= nodes.length) {
- dom.insertAfter(marker, nodes[nodes.length - 1]);
- } else {
- container.insertBefore(marker, nodes[offset]);
- }
- tmpRng.moveToElementText(marker);
- } else if (container.canHaveHTML) {
- // Empty node selection for example <div>|</div>
- // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
- container.innerHTML = '<span></span>';
- marker = container.firstChild;
- tmpRng.moveToElementText(marker);
- tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
- }
- ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
- dom.remove(marker);
- }
- }
- // Setup some shorter versions
- startContainer = rng.startContainer;
- startOffset = rng.startOffset;
- endContainer = rng.endContainer;
- endOffset = rng.endOffset;
- ieRng = body.createTextRange();
- // If single element selection then try making a control selection out of it
- if (startContainer == endContainer && startContainer.nodeType == 1) {
- // Trick to place the caret inside an empty block element like <p></p>
- if (startOffset == endOffset && !startContainer.hasChildNodes()) {
- if (startContainer.canHaveHTML) {
- // Check if previous sibling is an empty block if it is then we need to render it
- // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
- // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
- sibling = startContainer.previousSibling;
- if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
- sibling.innerHTML = '';
- } else {
- sibling = null;
- }
- startContainer.innerHTML = '<span></span><span></span>';
- ieRng.moveToElementText(startContainer.lastChild);
- ieRng.select();
- dom.doc.selection.clear();
- startContainer.innerHTML = '';
- if (sibling) {
- sibling.innerHTML = '';
- }
- return;
- }
- startOffset = dom.nodeIndex(startContainer);
- startContainer = startContainer.parentNode;
- }
- if (startOffset == endOffset - 1) {
- try {
- ctrlElm = startContainer.childNodes[startOffset];
- ctrlRng = body.createControlRange();
- ctrlRng.addElement(ctrlElm);
- ctrlRng.select();
- // Check if the range produced is on the correct element and is a control range
- // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
- nativeRng = selection.getRng();
- if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
- return;
- }
- } catch (ex) {
- // Ignore
- }
- }
- }
- // Set start/end point of selection
- setEndPoint(true);
- setEndPoint();
- // Select the new range and scroll it into view
- ieRng.select();
- };
- // Expose range method
- this.getRangeAt = getRange;
- }
- return Selection;
- });
- // Included from: js/tinymce/classes/util/VK.js
- /**
- * VK.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This file exposes a set of the common KeyCodes for use. Please grow it as needed.
- */
- define("tinymce/util/VK", [
- "tinymce/Env"
- ], function(Env) {
- return {
- BACKSPACE: 8,
- DELETE: 46,
- DOWN: 40,
- ENTER: 13,
- LEFT: 37,
- RIGHT: 39,
- SPACEBAR: 32,
- TAB: 9,
- UP: 38,
- modifierPressed: function(e) {
- return e.shiftKey || e.ctrlKey || e.altKey || this.metaKeyPressed(e);
- },
- metaKeyPressed: function(e) {
- // Check if ctrl or meta key is pressed. Edge case for AltGr on Windows where it produces ctrlKey+altKey states
- return (Env.mac ? e.metaKey : e.ctrlKey && !e.altKey);
- }
- };
- });
- // Included from: js/tinymce/classes/dom/ControlSelection.js
- /**
- * ControlSelection.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles control selection of elements. Controls are elements
- * that can be resized and needs to be selected as a whole. It adds custom resize handles
- * to all browser engines that support properly disabling the built in resize logic.
- *
- * @class tinymce.dom.ControlSelection
- */
- define("tinymce/dom/ControlSelection", [
- "tinymce/util/VK",
- "tinymce/util/Tools",
- "tinymce/util/Delay",
- "tinymce/Env",
- "tinymce/dom/NodeType"
- ], function(VK, Tools, Delay, Env, NodeType) {
- var isContentEditableFalse = NodeType.isContentEditableFalse;
- var isContentEditableTrue = NodeType.isContentEditableTrue;
- function getContentEditableRoot(root, node) {
- while (node && node != root) {
- if (isContentEditableTrue(node) || isContentEditableFalse(node)) {
- return node;
- }
- node = node.parentNode;
- }
- return null;
- }
- return function(selection, editor) {
- var dom = editor.dom, each = Tools.each;
- var selectedElm, selectedElmGhost, resizeHelper, resizeHandles, selectedHandle, lastMouseDownEvent;
- var startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
- var width, height, editableDoc = editor.getDoc(), rootDocument = document, isIE = Env.ie && Env.ie < 11;
- var abs = Math.abs, round = Math.round, rootElement = editor.getBody(), startScrollWidth, startScrollHeight;
- // Details about each resize handle how to scale etc
- resizeHandles = {
- // Name: x multiplier, y multiplier, delta size x, delta size y
- /*n: [0.5, 0, 0, -1],
- e: [1, 0.5, 1, 0],
- s: [0.5, 1, 0, 1],
- w: [0, 0.5, -1, 0],*/
- nw: [0, 0, -1, -1],
- ne: [1, 0, 1, -1],
- se: [1, 1, 1, 1],
- sw: [0, 1, -1, 1]
- };
- // Add CSS for resize handles, cloned element and selected
- var rootClass = '.mce-content-body';
- editor.contentStyles.push(
- rootClass + ' div.mce-resizehandle {' +
- 'position: absolute;' +
- 'border: 1px solid black;' +
- 'box-sizing: box-sizing;' +
- 'background: #FFF;' +
- 'width: 7px;' +
- 'height: 7px;' +
- 'z-index: 10000' +
- '}' +
- rootClass + ' .mce-resizehandle:hover {' +
- 'background: #000' +
- '}' +
- rootClass + ' img[data-mce-selected],' + rootClass + ' hr[data-mce-selected] {' +
- 'outline: 1px solid black;' +
- 'resize: none' + // Have been talks about implementing this in browsers
- '}' +
- rootClass + ' .mce-clonedresizable {' +
- 'position: absolute;' +
- (Env.gecko ? '' : 'outline: 1px dashed black;') + // Gecko produces trails while resizing
- 'opacity: .5;' +
- 'filter: alpha(opacity=50);' +
- 'z-index: 10000' +
- '}' +
- rootClass + ' .mce-resize-helper {' +
- 'background: #555;' +
- 'background: rgba(0,0,0,0.75);' +
- 'border-radius: 3px;' +
- 'border: 1px;' +
- 'color: white;' +
- 'display: none;' +
- 'font-family: sans-serif;' +
- 'font-size: 12px;' +
- 'white-space: nowrap;' +
- 'line-height: 14px;' +
- 'margin: 5px 10px;' +
- 'padding: 5px;' +
- 'position: absolute;' +
- 'z-index: 10001' +
- '}'
- );
- function isResizable(elm) {
- var selector = editor.settings.object_resizing;
- if (selector === false || Env.iOS) {
- return false;
- }
- if (typeof selector != 'string') {
- selector = 'table,img,div';
- }
- if (elm.getAttribute('data-mce-resize') === 'false') {
- return false;
- }
- if (elm == editor.getBody()) {
- return false;
- }
- return editor.dom.is(elm, selector);
- }
- function resizeGhostElement(e) {
- var deltaX, deltaY, proportional;
- var resizeHelperX, resizeHelperY;
- // Calc new width/height
- deltaX = e.screenX - startX;
- deltaY = e.screenY - startY;
- // Calc new size
- width = deltaX * selectedHandle[2] + startW;
- height = deltaY * selectedHandle[3] + startH;
- // Never scale down lower than 5 pixels
- width = width < 5 ? 5 : width;
- height = height < 5 ? 5 : height;
- if (selectedElm.nodeName == "IMG" && editor.settings.resize_img_proportional !== false) {
- proportional = !VK.modifierPressed(e);
- } else {
- proportional = VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0);
- }
- // Constrain proportions
- if (proportional) {
- if (abs(deltaX) > abs(deltaY)) {
- height = round(width * ratio);
- width = round(height / ratio);
- } else {
- width = round(height / ratio);
- height = round(width * ratio);
- }
- }
- // Update ghost size
- dom.setStyles(selectedElmGhost, {
- width: width,
- height: height
- });
- // Update resize helper position
- resizeHelperX = selectedHandle.startPos.x + deltaX;
- resizeHelperY = selectedHandle.startPos.y + deltaY;
- resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
- resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
- dom.setStyles(resizeHelper, {
- left: resizeHelperX,
- top: resizeHelperY,
- display: 'block'
- });
- resizeHelper.innerHTML = width + ' × ' + height;
- // Update ghost X position if needed
- if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
- dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
- }
- // Update ghost Y position if needed
- if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
- dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
- }
- // Calculate how must overflow we got
- deltaX = rootElement.scrollWidth - startScrollWidth;
- deltaY = rootElement.scrollHeight - startScrollHeight;
- // Re-position the resize helper based on the overflow
- if (deltaX + deltaY !== 0) {
- dom.setStyles(resizeHelper, {
- left: resizeHelperX - deltaX,
- top: resizeHelperY - deltaY
- });
- }
- if (!resizeStarted) {
- editor.fire('ObjectResizeStart', {target: selectedElm, width: startW, height: startH});
- resizeStarted = true;
- }
- }
- function endGhostResize() {
- resizeStarted = false;
- function setSizeProp(name, value) {
- if (value) {
- // Resize by using style or attribute
- if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
- dom.setStyle(selectedElm, name, value);
- } else {
- dom.setAttrib(selectedElm, name, value);
- }
- }
- }
- // Set width/height properties
- setSizeProp('width', width);
- setSizeProp('height', height);
- dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
- dom.unbind(editableDoc, 'mouseup', endGhostResize);
- if (rootDocument != editableDoc) {
- dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
- dom.unbind(rootDocument, 'mouseup', endGhostResize);
- }
- // Remove ghost/helper and update resize handle positions
- dom.remove(selectedElmGhost);
- dom.remove(resizeHelper);
- if (!isIE || selectedElm.nodeName == "TABLE") {
- showResizeRect(selectedElm);
- }
- editor.fire('ObjectResized', {target: selectedElm, width: width, height: height});
- dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
- editor.nodeChanged();
- }
- function showResizeRect(targetElm, mouseDownHandleName, mouseDownEvent) {
- var position, targetWidth, targetHeight, e, rect;
- hideResizeRect();
- unbindResizeHandleEvents();
- // Get position and size of target
- position = dom.getPos(targetElm, rootElement);
- selectedElmX = position.x;
- selectedElmY = position.y;
- rect = targetElm.getBoundingClientRect(); // Fix for Gecko offsetHeight for table with caption
- targetWidth = rect.width || (rect.right - rect.left);
- targetHeight = rect.height || (rect.bottom - rect.top);
- // Reset width/height if user selects a new image/table
- if (selectedElm != targetElm) {
- detachResizeStartListener();
- selectedElm = targetElm;
- width = height = 0;
- }
- // Makes it possible to disable resizing
- e = editor.fire('ObjectSelected', {target: targetElm});
- if (isResizable(targetElm) && !e.isDefaultPrevented()) {
- each(resizeHandles, function(handle, name) {
- var handleElm;
- function startDrag(e) {
- startX = e.screenX;
- startY = e.screenY;
- startW = selectedElm.clientWidth;
- startH = selectedElm.clientHeight;
- ratio = startH / startW;
- selectedHandle = handle;
- handle.startPos = {
- x: targetWidth * handle[0] + selectedElmX,
- y: targetHeight * handle[1] + selectedElmY
- };
- startScrollWidth = rootElement.scrollWidth;
- startScrollHeight = rootElement.scrollHeight;
- selectedElmGhost = selectedElm.cloneNode(true);
- dom.addClass(selectedElmGhost, 'mce-clonedresizable');
- dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
- selectedElmGhost.contentEditable = false; // Hides IE move layer cursor
- selectedElmGhost.unSelectabe = true;
- dom.setStyles(selectedElmGhost, {
- left: selectedElmX,
- top: selectedElmY,
- margin: 0
- });
- selectedElmGhost.removeAttribute('data-mce-selected');
- rootElement.appendChild(selectedElmGhost);
- dom.bind(editableDoc, 'mousemove', resizeGhostElement);
- dom.bind(editableDoc, 'mouseup', endGhostResize);
- if (rootDocument != editableDoc) {
- dom.bind(rootDocument, 'mousemove', resizeGhostElement);
- dom.bind(rootDocument, 'mouseup', endGhostResize);
- }
- resizeHelper = dom.add(rootElement, 'div', {
- 'class': 'mce-resize-helper',
- 'data-mce-bogus': 'all'
- }, startW + ' × ' + startH);
- }
- if (mouseDownHandleName) {
- // Drag started by IE native resizestart
- if (name == mouseDownHandleName) {
- startDrag(mouseDownEvent);
- }
- return;
- }
- // Get existing or render resize handle
- handleElm = dom.get('mceResizeHandle' + name);
- if (handleElm) {
- dom.remove(handleElm);
- }
- handleElm = dom.add(rootElement, 'div', {
- id: 'mceResizeHandle' + name,
- 'data-mce-bogus': 'all',
- 'class': 'mce-resizehandle',
- unselectable: true,
- style: 'cursor:' + name + '-resize; margin:0; padding:0'
- });
- // Hides IE move layer cursor
- // If we set it on Chrome we get this wounderful bug: #6725
- if (Env.ie) {
- handleElm.contentEditable = false;
- }
- dom.bind(handleElm, 'mousedown', function(e) {
- e.stopImmediatePropagation();
- e.preventDefault();
- startDrag(e);
- });
- handle.elm = handleElm;
- // Position element
- dom.setStyles(handleElm, {
- left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
- top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
- });
- });
- } else {
- hideResizeRect();
- }
- selectedElm.setAttribute('data-mce-selected', '1');
- }
- function hideResizeRect() {
- var name, handleElm;
- unbindResizeHandleEvents();
- if (selectedElm) {
- selectedElm.removeAttribute('data-mce-selected');
- }
- for (name in resizeHandles) {
- handleElm = dom.get('mceResizeHandle' + name);
- if (handleElm) {
- dom.unbind(handleElm);
- dom.remove(handleElm);
- }
- }
- }
- function updateResizeRect(e) {
- var startElm, controlElm;
- function isChildOrEqual(node, parent) {
- if (node) {
- do {
- if (node === parent) {
- return true;
- }
- } while ((node = node.parentNode));
- }
- }
- // Ignore all events while resizing or if the editor instance was removed
- if (resizeStarted || editor.removed) {
- return;
- }
- // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
- each(dom.select('img[data-mce-selected],hr[data-mce-selected]'), function(img) {
- img.removeAttribute('data-mce-selected');
- });
- controlElm = e.type == 'mousedown' ? e.target : selection.getNode();
- controlElm = dom.$(controlElm).closest(isIE ? 'table' : 'table,img,hr')[0];
- if (isChildOrEqual(controlElm, rootElement)) {
- disableGeckoResize();
- startElm = selection.getStart(true);
- if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
- if (!isIE || (controlElm != startElm && startElm.nodeName !== 'IMG')) {
- showResizeRect(controlElm);
- return;
- }
- }
- }
- hideResizeRect();
- }
- function attachEvent(elm, name, func) {
- if (elm && elm.attachEvent) {
- elm.attachEvent('on' + name, func);
- }
- }
- function detachEvent(elm, name, func) {
- if (elm && elm.detachEvent) {
- elm.detachEvent('on' + name, func);
- }
- }
- function resizeNativeStart(e) {
- var target = e.srcElement, pos, name, corner, cornerX, cornerY, relativeX, relativeY;
- pos = target.getBoundingClientRect();
- relativeX = lastMouseDownEvent.clientX - pos.left;
- relativeY = lastMouseDownEvent.clientY - pos.top;
- // Figure out what corner we are draging on
- for (name in resizeHandles) {
- corner = resizeHandles[name];
- cornerX = target.offsetWidth * corner[0];
- cornerY = target.offsetHeight * corner[1];
- if (abs(cornerX - relativeX) < 8 && abs(cornerY - relativeY) < 8) {
- selectedHandle = corner;
- break;
- }
- }
- // Remove native selection and let the magic begin
- resizeStarted = true;
- editor.fire('ObjectResizeStart', {
- target: selectedElm,
- width: selectedElm.clientWidth,
- height: selectedElm.clientHeight
- });
- editor.getDoc().selection.empty();
- showResizeRect(target, name, lastMouseDownEvent);
- }
- function preventDefault(e) {
- if (e.preventDefault) {
- e.preventDefault();
- } else {
- e.returnValue = false; // IE
- }
- }
- function isWithinContentEditableFalse(elm) {
- return isContentEditableFalse(getContentEditableRoot(editor.getBody(), elm));
- }
- function nativeControlSelect(e) {
- var target = e.srcElement;
- if (isWithinContentEditableFalse(target)) {
- preventDefault(e);
- return;
- }
- if (target != selectedElm) {
- editor.fire('ObjectSelected', {target: target});
- detachResizeStartListener();
- if (target.id.indexOf('mceResizeHandle') === 0) {
- e.returnValue = false;
- return;
- }
- if (target.nodeName == 'IMG' || target.nodeName == 'TABLE') {
- hideResizeRect();
- selectedElm = target;
- attachEvent(target, 'resizestart', resizeNativeStart);
- }
- }
- }
- function detachResizeStartListener() {
- detachEvent(selectedElm, 'resizestart', resizeNativeStart);
- }
- function unbindResizeHandleEvents() {
- for (var name in resizeHandles) {
- var handle = resizeHandles[name];
- if (handle.elm) {
- dom.unbind(handle.elm);
- delete handle.elm;
- }
- }
- }
- function disableGeckoResize() {
- try {
- // Disable object resizing on Gecko
- editor.getDoc().execCommand('enableObjectResizing', false, false);
- } catch (ex) {
- // Ignore
- }
- }
- function controlSelect(elm) {
- var ctrlRng;
- if (!isIE) {
- return;
- }
- ctrlRng = editableDoc.body.createControlRange();
- try {
- ctrlRng.addElement(elm);
- ctrlRng.select();
- return true;
- } catch (ex) {
- // Ignore since the element can't be control selected for example a P tag
- }
- }
- editor.on('init', function() {
- if (isIE) {
- // Hide the resize rect on resize and reselect the image
- editor.on('ObjectResized', function(e) {
- if (e.target.nodeName != 'TABLE') {
- hideResizeRect();
- controlSelect(e.target);
- }
- });
- attachEvent(rootElement, 'controlselect', nativeControlSelect);
- editor.on('mousedown', function(e) {
- lastMouseDownEvent = e;
- });
- } else {
- disableGeckoResize();
- // Sniff sniff, hard to feature detect this stuff
- if (Env.ie >= 11) {
- // Needs to be mousedown for drag/drop to work on IE 11
- // Needs to be click on Edge to properly select images
- editor.on('mousedown click', function(e) {
- var target = e.target, nodeName = target.nodeName;
- if (!resizeStarted && /^(TABLE|IMG|HR)$/.test(nodeName) && !isWithinContentEditableFalse(target)) {
- editor.selection.select(target, nodeName == 'TABLE');
- // Only fire once since nodeChange is expensive
- if (e.type == 'mousedown') {
- editor.nodeChanged();
- }
- }
- });
- editor.dom.bind(rootElement, 'mscontrolselect', function(e) {
- function delayedSelect(node) {
- Delay.setEditorTimeout(editor, function() {
- editor.selection.select(node);
- });
- }
- if (isWithinContentEditableFalse(e.target)) {
- e.preventDefault();
- delayedSelect(e.target);
- return;
- }
- if (/^(TABLE|IMG|HR)$/.test(e.target.nodeName)) {
- e.preventDefault();
- // This moves the selection from being a control selection to a text like selection like in WebKit #6753
- // TODO: Fix this the day IE works like other browsers without this nasty native ugly control selections.
- if (e.target.tagName == 'IMG') {
- delayedSelect(e.target);
- }
- }
- });
- }
- }
- var throttledUpdateResizeRect = Delay.throttle(function(e) {
- if (!editor.composing) {
- updateResizeRect(e);
- }
- });
- editor.on('nodechange ResizeEditor ResizeWindow drop', throttledUpdateResizeRect);
- // Update resize rect while typing in a table
- editor.on('keyup compositionend', function(e) {
- // Don't update the resize rect while composing since it blows away the IME see: #2710
- if (selectedElm && selectedElm.nodeName == "TABLE") {
- throttledUpdateResizeRect(e);
- }
- });
- editor.on('hide blur', hideResizeRect);
- // Hide rect on focusout since it would float on top of windows otherwise
- //editor.on('focusout', hideResizeRect);
- });
- editor.on('remove', unbindResizeHandleEvents);
- function destroy() {
- selectedElm = selectedElmGhost = null;
- if (isIE) {
- detachResizeStartListener();
- detachEvent(rootElement, 'controlselect', nativeControlSelect);
- }
- }
- return {
- isResizable: isResizable,
- showResizeRect: showResizeRect,
- hideResizeRect: hideResizeRect,
- updateResizeRect: updateResizeRect,
- controlSelect: controlSelect,
- destroy: destroy
- };
- };
- });
- // Included from: js/tinymce/classes/util/Fun.js
- /**
- * Fun.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Functional utility class.
- *
- * @private
- * @class tinymce.util.Fun
- */
- define("tinymce/util/Fun", [], function() {
- var slice = [].slice;
- function constant(value) {
- return function() {
- return value;
- };
- }
- function negate(predicate) {
- return function(x) {
- return !predicate(x);
- };
- }
- function compose(f, g) {
- return function(x) {
- return f(g(x));
- };
- }
- function or() {
- var args = slice.call(arguments);
- return function(x) {
- for (var i = 0; i < args.length; i++) {
- if (args[i](x)) {
- return true;
- }
- }
- return false;
- };
- }
- function and() {
- var args = slice.call(arguments);
- return function(x) {
- for (var i = 0; i < args.length; i++) {
- if (!args[i](x)) {
- return false;
- }
- }
- return true;
- };
- }
- function curry(fn) {
- var args = slice.call(arguments);
- if (args.length - 1 >= fn.length) {
- return fn.apply(this, args.slice(1));
- }
- return function() {
- var tempArgs = args.concat([].slice.call(arguments));
- return curry.apply(this, tempArgs);
- };
- }
- return {
- constant: constant,
- negate: negate,
- and: and,
- or: or,
- curry: curry,
- compose: compose
- };
- });
- // Included from: js/tinymce/classes/caret/CaretCandidate.js
- /**
- * CaretCandidate.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module contains logic for handling caret candidates. A caret candidate is
- * for example text nodes, images, input elements, cE=false elements etc.
- *
- * @private
- * @class tinymce.caret.CaretCandidate
- */
- define("tinymce/caret/CaretCandidate", [
- "tinymce/dom/NodeType",
- "tinymce/util/Arr",
- "tinymce/caret/CaretContainer"
- ], function(NodeType, Arr, CaretContainer) {
- var isContentEditableTrue = NodeType.isContentEditableTrue,
- isContentEditableFalse = NodeType.isContentEditableFalse,
- isBr = NodeType.isBr,
- isText = NodeType.isText,
- isInvalidTextElement = NodeType.matchNodeNames('script style textarea'),
- isAtomicInline = NodeType.matchNodeNames('img input textarea hr iframe video audio object'),
- isTable = NodeType.matchNodeNames('table'),
- isCaretContainer = CaretContainer.isCaretContainer;
- function isCaretCandidate(node) {
- if (isCaretContainer(node)) {
- return false;
- }
- if (isText(node)) {
- if (isInvalidTextElement(node.parentNode)) {
- return false;
- }
- return true;
- }
- return isAtomicInline(node) || isBr(node) || isTable(node) || isContentEditableFalse(node);
- }
- function isInEditable(node, rootNode) {
- for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
- if (isContentEditableFalse(node)) {
- return false;
- }
- if (isContentEditableTrue(node)) {
- return true;
- }
- }
- return true;
- }
- function isAtomicContentEditableFalse(node) {
- if (!isContentEditableFalse(node)) {
- return false;
- }
- return Arr.reduce(node.getElementsByTagName('*'), function(result, elm) {
- return result || isContentEditableTrue(elm);
- }, false) !== true;
- }
- function isAtomic(node) {
- return isAtomicInline(node) || isAtomicContentEditableFalse(node);
- }
- function isEditableCaretCandidate(node, rootNode) {
- return isCaretCandidate(node) && isInEditable(node, rootNode);
- }
- return {
- isCaretCandidate: isCaretCandidate,
- isInEditable: isInEditable,
- isAtomic: isAtomic,
- isEditableCaretCandidate: isEditableCaretCandidate
- };
- });
- // Included from: js/tinymce/classes/geom/ClientRect.js
- /**
- * ClientRect.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility functions for working with client rects.
- *
- * @private
- * @class tinymce.geom.ClientRect
- */
- define("tinymce/geom/ClientRect", [], function() {
- var round = Math.round;
- function clone(rect) {
- if (!rect) {
- return {left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0};
- }
- return {
- left: round(rect.left),
- top: round(rect.top),
- bottom: round(rect.bottom),
- right: round(rect.right),
- width: round(rect.width),
- height: round(rect.height)
- };
- }
- function collapse(clientRect, toStart) {
- clientRect = clone(clientRect);
- if (toStart) {
- clientRect.right = clientRect.left;
- } else {
- clientRect.left = clientRect.left + clientRect.width;
- clientRect.right = clientRect.left;
- }
- clientRect.width = 0;
- return clientRect;
- }
- function isEqual(rect1, rect2) {
- return (
- rect1.left === rect2.left &&
- rect1.top === rect2.top &&
- rect1.bottom === rect2.bottom &&
- rect1.right === rect2.right
- );
- }
- function isValidOverflow(overflowY, clientRect1, clientRect2) {
- return overflowY >= 0 && overflowY <= Math.min(clientRect1.height, clientRect2.height) / 2;
- }
- function isAbove(clientRect1, clientRect2) {
- if (clientRect1.bottom < clientRect2.top) {
- return true;
- }
- if (clientRect1.top > clientRect2.bottom) {
- return false;
- }
- return isValidOverflow(clientRect2.top - clientRect1.bottom, clientRect1, clientRect2);
- }
- function isBelow(clientRect1, clientRect2) {
- if (clientRect1.top > clientRect2.bottom) {
- return true;
- }
- if (clientRect1.bottom < clientRect2.top) {
- return false;
- }
- return isValidOverflow(clientRect2.bottom - clientRect1.top, clientRect1, clientRect2);
- }
- function isLeft(clientRect1, clientRect2) {
- return clientRect1.left < clientRect2.left;
- }
- function isRight(clientRect1, clientRect2) {
- return clientRect1.right > clientRect2.right;
- }
- function compare(clientRect1, clientRect2) {
- if (isAbove(clientRect1, clientRect2)) {
- return -1;
- }
- if (isBelow(clientRect1, clientRect2)) {
- return 1;
- }
- if (isLeft(clientRect1, clientRect2)) {
- return -1;
- }
- if (isRight(clientRect1, clientRect2)) {
- return 1;
- }
- return 0;
- }
- function containsXY(clientRect, clientX, clientY) {
- return (
- clientX >= clientRect.left &&
- clientX <= clientRect.right &&
- clientY >= clientRect.top &&
- clientY <= clientRect.bottom
- );
- }
- return {
- clone: clone,
- collapse: collapse,
- isEqual: isEqual,
- isAbove: isAbove,
- isBelow: isBelow,
- isLeft: isLeft,
- isRight: isRight,
- compare: compare,
- containsXY: containsXY
- };
- });
- // Included from: js/tinymce/classes/text/ExtendingChar.js
- /**
- * ExtendingChar.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class contains logic for detecting extending characters.
- *
- * @private
- * @class tinymce.text.ExtendingChar
- * @example
- * var isExtending = ExtendingChar.isExtendingChar('a');
- */
- define("tinymce/text/ExtendingChar", [], function() {
- // Generated from: http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
- // Only includes the characters in that fit into UCS-2 16 bit
- var extendingChars = new RegExp(
- "[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A" +
- "\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0" +
- "\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0902\u093A\u093C" +
- "\u0941-\u0948\u094D\u0951-\u0957\u0962-\u0963\u0981\u09BC\u09BE\u09C1-\u09C4\u09CD\u09D7\u09E2-\u09E3" +
- "\u0A01-\u0A02\u0A3C\u0A41-\u0A42\u0A47-\u0A48\u0A4B-\u0A4D\u0A51\u0A70-\u0A71\u0A75\u0A81-\u0A82\u0ABC" +
- "\u0AC1-\u0AC5\u0AC7-\u0AC8\u0ACD\u0AE2-\u0AE3\u0B01\u0B3C\u0B3E\u0B3F\u0B41-\u0B44\u0B4D\u0B56\u0B57" +
- "\u0B62-\u0B63\u0B82\u0BBE\u0BC0\u0BCD\u0BD7\u0C00\u0C3E-\u0C40\u0C46-\u0C48\u0C4A-\u0C4D\u0C55-\u0C56" +
- "\u0C62-\u0C63\u0C81\u0CBC\u0CBF\u0CC2\u0CC6\u0CCC-\u0CCD\u0CD5-\u0CD6\u0CE2-\u0CE3\u0D01\u0D3E\u0D41-\u0D44" +
- "\u0D4D\u0D57\u0D62-\u0D63\u0DCA\u0DCF\u0DD2-\u0DD4\u0DD6\u0DDF\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9" +
- "\u0EBB-\u0EBC\u0EC8-\u0ECD\u0F18-\u0F19\u0F35\u0F37\u0F39\u0F71-\u0F7E\u0F80-\u0F84\u0F86-\u0F87\u0F8D-\u0F97" +
- "\u0F99-\u0FBC\u0FC6\u102D-\u1030\u1032-\u1037\u1039-\u103A\u103D-\u103E\u1058-\u1059\u105E-\u1060\u1071-\u1074" +
- "\u1082\u1085-\u1086\u108D\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17B4-\u17B5" +
- "\u17B7-\u17BD\u17C6\u17C9-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193B\u1A17-\u1A18" +
- "\u1A1B\u1A56\u1A58-\u1A5E\u1A60\u1A62\u1A65-\u1A6C\u1A73-\u1A7C\u1A7F\u1AB0-\u1ABD\u1ABE\u1B00-\u1B03\u1B34" +
- "\u1B36-\u1B3A\u1B3C\u1B42\u1B6B-\u1B73\u1B80-\u1B81\u1BA2-\u1BA5\u1BA8-\u1BA9\u1BAB-\u1BAD\u1BE6\u1BE8-\u1BE9" +
- "\u1BED\u1BEF-\u1BF1\u1C2C-\u1C33\u1C36-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8-\u1CF9" +
- "\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C-\u200D\u20D0-\u20DC\u20DD-\u20E0\u20E1\u20E2-\u20E4\u20E5-\u20F0\u2CEF-\u2CF1" +
- "\u2D7F\u2DE0-\u2DFF\u302A-\u302D\u302E-\u302F\u3099-\u309A\uA66F\uA670-\uA672\uA674-\uA67D\uA69E-\uA69F\uA6F0-\uA6F1" +
- "\uA802\uA806\uA80B\uA825-\uA826\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA951\uA980-\uA982\uA9B3\uA9B6-\uA9B9\uA9BC" +
- "\uA9E5\uAA29-\uAA2E\uAA31-\uAA32\uAA35-\uAA36\uAA43\uAA4C\uAA7C\uAAB0\uAAB2-\uAAB4\uAAB7-\uAAB8\uAABE-\uAABF\uAAC1" +
- "\uAAEC-\uAAED\uAAF6\uABE5\uABE8\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F\uFF9E-\uFF9F]"
- );
- function isExtendingChar(ch) {
- return typeof ch == "string" && ch.charCodeAt(0) >= 768 && extendingChars.test(ch);
- }
- return {
- isExtendingChar: isExtendingChar
- };
- });
- // Included from: js/tinymce/classes/caret/CaretPosition.js
- /**
- * CaretPosition.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module contains logic for creating caret positions within a document a caretposition
- * is similar to a DOMRange object but it doesn't have two endpoints and is also more lightweight
- * since it's now updated live when the DOM changes.
- *
- * @private
- * @class tinymce.caret.CaretPosition
- * @example
- * var caretPos1 = new CaretPosition(container, offset);
- * var caretPos2 = CaretPosition.fromRangeStart(someRange);
- */
- define("tinymce/caret/CaretPosition", [
- "tinymce/util/Fun",
- "tinymce/dom/NodeType",
- "tinymce/dom/DOMUtils",
- "tinymce/dom/RangeUtils",
- "tinymce/caret/CaretCandidate",
- "tinymce/geom/ClientRect",
- "tinymce/text/ExtendingChar"
- ], function(Fun, NodeType, DOMUtils, RangeUtils, CaretCandidate, ClientRect, ExtendingChar) {
- var isElement = NodeType.isElement,
- isCaretCandidate = CaretCandidate.isCaretCandidate,
- isBlock = NodeType.matchStyleValues('display', 'block table'),
- isFloated = NodeType.matchStyleValues('float', 'left right'),
- isValidElementCaretCandidate = Fun.and(isElement, isCaretCandidate, Fun.negate(isFloated)),
- isNotPre = Fun.negate(NodeType.matchStyleValues('white-space', 'pre pre-line pre-wrap')),
- isText = NodeType.isText,
- isBr = NodeType.isBr,
- nodeIndex = DOMUtils.nodeIndex,
- resolveIndex = RangeUtils.getNode;
- function createRange(doc) {
- return "createRange" in doc ? doc.createRange() : DOMUtils.DOM.createRng();
- }
- function isWhiteSpace(chr) {
- return chr && /[\r\n\t ]/.test(chr);
- }
- function isHiddenWhiteSpaceRange(range) {
- var container = range.startContainer,
- offset = range.startOffset,
- text;
- if (isWhiteSpace(range.toString()) && isNotPre(container.parentNode)) {
- text = container.data;
- if (isWhiteSpace(text[offset - 1]) || isWhiteSpace(text[offset + 1])) {
- return true;
- }
- }
- return false;
- }
- function getCaretPositionClientRects(caretPosition) {
- var clientRects = [], beforeNode, node;
- // Hack for older WebKit versions that doesn't
- // support getBoundingClientRect on BR elements
- function getBrClientRect(brNode) {
- var doc = brNode.ownerDocument,
- rng = createRange(doc),
- nbsp = doc.createTextNode('\u00a0'),
- parentNode = brNode.parentNode,
- clientRect;
- parentNode.insertBefore(nbsp, brNode);
- rng.setStart(nbsp, 0);
- rng.setEnd(nbsp, 1);
- clientRect = ClientRect.clone(rng.getBoundingClientRect());
- parentNode.removeChild(nbsp);
- return clientRect;
- }
- function getBoundingClientRect(item) {
- var clientRect, clientRects;
- clientRects = item.getClientRects();
- if (clientRects.length > 0) {
- clientRect = ClientRect.clone(clientRects[0]);
- } else {
- clientRect = ClientRect.clone(item.getBoundingClientRect());
- }
- if (isBr(item) && clientRect.left === 0) {
- return getBrClientRect(item);
- }
- return clientRect;
- }
- function collapseAndInflateWidth(clientRect, toStart) {
- clientRect = ClientRect.collapse(clientRect, toStart);
- clientRect.width = 1;
- clientRect.right = clientRect.left + 1;
- return clientRect;
- }
- function addUniqueAndValidRect(clientRect) {
- if (clientRect.height === 0) {
- return;
- }
- if (clientRects.length > 0) {
- if (ClientRect.isEqual(clientRect, clientRects[clientRects.length - 1])) {
- return;
- }
- }
- clientRects.push(clientRect);
- }
- function addCharacterOffset(container, offset) {
- var range = createRange(container.ownerDocument);
- if (offset < container.data.length) {
- if (ExtendingChar.isExtendingChar(container.data[offset])) {
- return clientRects;
- }
- // WebKit returns two client rects for a position after an extending
- // character a\uxxx|b so expand on "b" and collapse to start of "b" box
- if (ExtendingChar.isExtendingChar(container.data[offset - 1])) {
- range.setStart(container, offset);
- range.setEnd(container, offset + 1);
- if (!isHiddenWhiteSpaceRange(range)) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
- return clientRects;
- }
- }
- }
- if (offset > 0) {
- range.setStart(container, offset - 1);
- range.setEnd(container, offset);
- if (!isHiddenWhiteSpaceRange(range)) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
- }
- }
- if (offset < container.data.length) {
- range.setStart(container, offset);
- range.setEnd(container, offset + 1);
- if (!isHiddenWhiteSpaceRange(range)) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), true));
- }
- }
- }
- if (isText(caretPosition.container())) {
- addCharacterOffset(caretPosition.container(), caretPosition.offset());
- return clientRects;
- }
- if (isElement(caretPosition.container())) {
- if (caretPosition.isAtEnd()) {
- node = resolveIndex(caretPosition.container(), caretPosition.offset());
- if (isText(node)) {
- addCharacterOffset(node, node.data.length);
- }
- if (isValidElementCaretCandidate(node) && !isBr(node)) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
- }
- } else {
- node = resolveIndex(caretPosition.container(), caretPosition.offset());
- if (isText(node)) {
- addCharacterOffset(node, 0);
- }
- if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
- return clientRects;
- }
- beforeNode = resolveIndex(caretPosition.container(), caretPosition.offset() - 1);
- if (isValidElementCaretCandidate(beforeNode) && !isBr(beforeNode)) {
- if (isBlock(beforeNode) || isBlock(node) || !isValidElementCaretCandidate(node)) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(beforeNode), false));
- }
- }
- if (isValidElementCaretCandidate(node)) {
- addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), true));
- }
- }
- }
- return clientRects;
- }
- /**
- * Represents a location within the document by a container and an offset.
- *
- * @constructor
- * @param {Node} container Container node.
- * @param {Number} offset Offset within that container node.
- * @param {Array} clientRects Optional client rects array for the position.
- */
- function CaretPosition(container, offset, clientRects) {
- function isAtStart() {
- if (isText(container)) {
- return offset === 0;
- }
- return offset === 0;
- }
- function isAtEnd() {
- if (isText(container)) {
- return offset >= container.data.length;
- }
- return offset >= container.childNodes.length;
- }
- function toRange() {
- var range;
- range = createRange(container.ownerDocument);
- range.setStart(container, offset);
- range.setEnd(container, offset);
- return range;
- }
- function getClientRects() {
- if (!clientRects) {
- clientRects = getCaretPositionClientRects(new CaretPosition(container, offset));
- }
- return clientRects;
- }
- function isVisible() {
- return getClientRects().length > 0;
- }
- function isEqual(caretPosition) {
- return caretPosition && container === caretPosition.container() && offset === caretPosition.offset();
- }
- function getNode(before) {
- return resolveIndex(container, before ? offset - 1 : offset);
- }
- return {
- /**
- * Returns the container node.
- *
- * @method container
- * @return {Node} Container node.
- */
- container: Fun.constant(container),
- /**
- * Returns the offset within the container node.
- *
- * @method offset
- * @return {Number} Offset within the container node.
- */
- offset: Fun.constant(offset),
- /**
- * Returns a range out of a the caret position.
- *
- * @method toRange
- * @return {DOMRange} range for the caret position.
- */
- toRange: toRange,
- /**
- * Returns the client rects for the caret position. Might be multiple rects between
- * block elements.
- *
- * @method getClientRects
- * @return {Array} Array of client rects.
- */
- getClientRects: getClientRects,
- /**
- * Returns true if the caret location is visible/displayed on screen.
- *
- * @method isVisible
- * @return {Boolean} true/false if the position is visible or not.
- */
- isVisible: isVisible,
- /**
- * Returns true if the caret location is at the beginning of text node or container.
- *
- * @method isVisible
- * @return {Boolean} true/false if the position is at the beginning.
- */
- isAtStart: isAtStart,
- /**
- * Returns true if the caret location is at the end of text node or container.
- *
- * @method isVisible
- * @return {Boolean} true/false if the position is at the end.
- */
- isAtEnd: isAtEnd,
- /**
- * Compares the caret position to another caret position. This will only compare the
- * container and offset not it's visual position.
- *
- * @method isEqual
- * @param {tinymce.caret.CaretPosition} caretPosition Caret position to compare with.
- * @return {Boolean} true if the caret positions are equal.
- */
- isEqual: isEqual,
- /**
- * Returns the closest resolved node from a node index. That means if you have an offset after the
- * last node in a container it will return that last node.
- *
- * @method getNode
- * @return {Node} Node that is closest to the index.
- */
- getNode: getNode
- };
- }
- /**
- * Creates a caret position from the start of a range.
- *
- * @method fromRangeStart
- * @param {DOMRange} range DOM Range to create caret position from.
- * @return {tinymce.caret.CaretPosition} Caret position from the start of DOM range.
- */
- CaretPosition.fromRangeStart = function(range) {
- return new CaretPosition(range.startContainer, range.startOffset);
- };
- /**
- * Creates a caret position from the end of a range.
- *
- * @method fromRangeEnd
- * @param {DOMRange} range DOM Range to create caret position from.
- * @return {tinymce.caret.CaretPosition} Caret position from the end of DOM range.
- */
- CaretPosition.fromRangeEnd = function(range) {
- return new CaretPosition(range.endContainer, range.endOffset);
- };
- /**
- * Creates a caret position from a node and places the offset after it.
- *
- * @method after
- * @param {Node} node Node to get caret position from.
- * @return {tinymce.caret.CaretPosition} Caret position from the node.
- */
- CaretPosition.after = function(node) {
- return new CaretPosition(node.parentNode, nodeIndex(node) + 1);
- };
- /**
- * Creates a caret position from a node and places the offset before it.
- *
- * @method before
- * @param {Node} node Node to get caret position from.
- * @return {tinymce.caret.CaretPosition} Caret position from the node.
- */
- CaretPosition.before = function(node) {
- return new CaretPosition(node.parentNode, nodeIndex(node));
- };
- return CaretPosition;
- });
- // Included from: js/tinymce/classes/caret/CaretBookmark.js
- /**
- * CaretBookmark.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module creates or resolves xpath like string representation of a CaretPositions.
- *
- * The format is a / separated list of chunks with:
- * <element|text()>[index|after|before]
- *
- * For example:
- * p[0]/b[0]/text()[0],1 = <p><b>a|c</b></p>
- * p[0]/img[0],before = <p>|<img></p>
- * p[0]/img[0],after = <p><img>|</p>
- *
- * @private
- * @static
- * @class tinymce.caret.CaretBookmark
- * @example
- * var bookmark = CaretBookmark.create(rootElm, CaretPosition.before(rootElm.firstChild));
- * var caretPosition = CaretBookmark.resolve(bookmark);
- */
- define('tinymce/caret/CaretBookmark', [
- 'tinymce/dom/NodeType',
- 'tinymce/dom/DOMUtils',
- 'tinymce/util/Fun',
- 'tinymce/util/Arr',
- 'tinymce/caret/CaretPosition'
- ], function(NodeType, DomUtils, Fun, Arr, CaretPosition) {
- var isText = NodeType.isText,
- isBogus = NodeType.isBogus,
- nodeIndex = DomUtils.nodeIndex;
- function normalizedParent(node) {
- var parentNode = node.parentNode;
- if (isBogus(parentNode)) {
- return normalizedParent(parentNode);
- }
- return parentNode;
- }
- function getChildNodes(node) {
- if (!node) {
- return [];
- }
- return Arr.reduce(node.childNodes, function(result, node) {
- if (isBogus(node) && node.nodeName != 'BR') {
- result = result.concat(getChildNodes(node));
- } else {
- result.push(node);
- }
- return result;
- }, []);
- }
- function normalizedTextOffset(textNode, offset) {
- while ((textNode = textNode.previousSibling)) {
- if (!isText(textNode)) {
- break;
- }
- offset += textNode.data.length;
- }
- return offset;
- }
- function equal(targetValue) {
- return function(value) {
- return targetValue === value;
- };
- }
- function normalizedNodeIndex(node) {
- var nodes, index, numTextFragments;
- nodes = getChildNodes(normalizedParent(node));
- index = Arr.findIndex(nodes, equal(node), node);
- nodes = nodes.slice(0, index + 1);
- numTextFragments = Arr.reduce(nodes, function(result, node, i) {
- if (isText(node) && isText(nodes[i - 1])) {
- result++;
- }
- return result;
- }, 0);
- nodes = Arr.filter(nodes, NodeType.matchNodeNames(node.nodeName));
- index = Arr.findIndex(nodes, equal(node), node);
- return index - numTextFragments;
- }
- function createPathItem(node) {
- var name;
- if (isText(node)) {
- name = 'text()';
- } else {
- name = node.nodeName.toLowerCase();
- }
- return name + '[' + normalizedNodeIndex(node) + ']';
- }
- function parentsUntil(rootNode, node, predicate) {
- var parents = [];
- for (node = node.parentNode; node != rootNode; node = node.parentNode) {
- if (predicate && predicate(node)) {
- break;
- }
- parents.push(node);
- }
- return parents;
- }
- function create(rootNode, caretPosition) {
- var container, offset, path = [],
- outputOffset, childNodes, parents;
- container = caretPosition.container();
- offset = caretPosition.offset();
- if (isText(container)) {
- outputOffset = normalizedTextOffset(container, offset);
- } else {
- childNodes = container.childNodes;
- if (offset >= childNodes.length) {
- outputOffset = 'after';
- offset = childNodes.length - 1;
- } else {
- outputOffset = 'before';
- }
- container = childNodes[offset];
- }
- path.push(createPathItem(container));
- parents = parentsUntil(rootNode, container);
- parents = Arr.filter(parents, Fun.negate(NodeType.isBogus));
- path = path.concat(Arr.map(parents, function(node) {
- return createPathItem(node);
- }));
- return path.reverse().join('/') + ',' + outputOffset;
- }
- function resolvePathItem(node, name, index) {
- var nodes = getChildNodes(node);
- nodes = Arr.filter(nodes, function(node, index) {
- return !isText(node) || !isText(nodes[index - 1]);
- });
- nodes = Arr.filter(nodes, NodeType.matchNodeNames(name));
- return nodes[index];
- }
- function findTextPosition(container, offset) {
- var node = container, targetOffset = 0, dataLen;
- while (isText(node)) {
- dataLen = node.data.length;
- if (offset >= targetOffset && offset <= targetOffset + dataLen) {
- container = node;
- offset = offset - targetOffset;
- break;
- }
- if (!isText(node.nextSibling)) {
- container = node;
- offset = dataLen;
- break;
- }
- targetOffset += dataLen;
- node = node.nextSibling;
- }
- if (offset > container.data.length) {
- offset = container.data.length;
- }
- return new CaretPosition(container, offset);
- }
- function resolve(rootNode, path) {
- var parts, container, offset;
- if (!path) {
- return null;
- }
- parts = path.split(',');
- path = parts[0].split('/');
- offset = parts.length > 1 ? parts[1] : 'before';
- container = Arr.reduce(path, function(result, value) {
- value = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value);
- if (!value) {
- return null;
- }
- if (value[1] === 'text()') {
- value[1] = '#text';
- }
- return resolvePathItem(result, value[1], parseInt(value[2], 10));
- }, rootNode);
- if (!container) {
- return null;
- }
- if (!isText(container)) {
- if (offset === 'after') {
- offset = nodeIndex(container) + 1;
- } else {
- offset = nodeIndex(container);
- }
- return new CaretPosition(container.parentNode, offset);
- }
- return findTextPosition(container, parseInt(offset, 10));
- }
- return {
- /**
- * Create a xpath bookmark location for the specified caret position.
- *
- * @method create
- * @param {Node} rootNode Root node to create bookmark within.
- * @param {tinymce.caret.CaretPosition} caretPosition Caret position within the root node.
- * @return {String} String xpath like location of caret position.
- */
- create: create,
- /**
- * Resolves a xpath like bookmark location to the a caret position.
- *
- * @method resolve
- * @param {Node} rootNode Root node to resolve xpath bookmark within.
- * @param {String} bookmark Bookmark string to resolve.
- * @return {tinymce.caret.CaretPosition} Caret position resolved from xpath like bookmark.
- */
- resolve: resolve
- };
- });
- // Included from: js/tinymce/classes/dom/BookmarkManager.js
- /**
- * BookmarkManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles selection bookmarks.
- *
- * @class tinymce.dom.BookmarkManager
- */
- define("tinymce/dom/BookmarkManager", [
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/caret/CaretContainer",
- "tinymce/caret/CaretBookmark",
- "tinymce/caret/CaretPosition",
- "tinymce/dom/NodeType"
- ], function(Env, Tools, CaretContainer, CaretBookmark, CaretPosition, NodeType) {
- var isContentEditableFalse = NodeType.isContentEditableFalse;
- /**
- * Constructs a new BookmarkManager instance for a specific selection instance.
- *
- * @constructor
- * @method BookmarkManager
- * @param {tinymce.dom.Selection} selection Selection instance to handle bookmarks for.
- */
- function BookmarkManager(selection) {
- var dom = selection.dom;
- /**
- * Returns a bookmark location for the current selection. This bookmark object
- * can then be used to restore the selection after some content modification to the document.
- *
- * @method getBookmark
- * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
- * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
- * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
- * @example
- * // Stores a bookmark of the current selection
- * var bm = tinymce.activeEditor.selection.getBookmark();
- *
- * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
- *
- * // Restore the selection bookmark
- * tinymce.activeEditor.selection.moveToBookmark(bm);
- */
- this.getBookmark = function(type, normalized) {
- var rng, rng2, id, collapsed, name, element, chr = '', styles;
- function findIndex(name, element) {
- var count = 0;
- Tools.each(dom.select(name), function(node) {
- if (node.getAttribute('data-mce-bogus') === 'all') {
- return;
- }
- if (node == element) {
- return false;
- }
- count++;
- });
- return count;
- }
- function normalizeTableCellSelection(rng) {
- function moveEndPoint(start) {
- var container, offset, childNodes, prefix = start ? 'start' : 'end';
- container = rng[prefix + 'Container'];
- offset = rng[prefix + 'Offset'];
- if (container.nodeType == 1 && container.nodeName == "TR") {
- childNodes = container.childNodes;
- container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
- if (container) {
- offset = start ? 0 : container.childNodes.length;
- rng['set' + (start ? 'Start' : 'End')](container, offset);
- }
- }
- }
- moveEndPoint(true);
- moveEndPoint();
- return rng;
- }
- function getLocation(rng) {
- var root = dom.getRoot(), bookmark = {};
- function getPoint(rng, start) {
- var container = rng[start ? 'startContainer' : 'endContainer'],
- offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
- if (container.nodeType == 3) {
- if (normalized) {
- for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) {
- offset += node.nodeValue.length;
- }
- }
- point.push(offset);
- } else {
- childNodes = container.childNodes;
- if (offset >= childNodes.length && childNodes.length) {
- after = 1;
- offset = Math.max(0, childNodes.length - 1);
- }
- point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
- }
- for (; container && container != root; container = container.parentNode) {
- point.push(dom.nodeIndex(container, normalized));
- }
- return point;
- }
- bookmark.start = getPoint(rng, true);
- if (!selection.isCollapsed()) {
- bookmark.end = getPoint(rng);
- }
- return bookmark;
- }
- function findAdjacentContentEditableFalseElm(rng) {
- function findSibling(node) {
- var sibling;
- if (CaretContainer.isCaretContainer(node)) {
- if (NodeType.isText(node) && CaretContainer.isCaretContainerBlock(node)) {
- node = node.parentNode;
- }
- sibling = node.previousSibling;
- if (isContentEditableFalse(sibling)) {
- return sibling;
- }
- sibling = node.nextSibling;
- if (isContentEditableFalse(sibling)) {
- return sibling;
- }
- }
- }
- return findSibling(rng.startContainer) || findSibling(rng.endContainer);
- }
- if (type == 2) {
- element = selection.getNode();
- name = element ? element.nodeName : null;
- rng = selection.getRng();
- if (isContentEditableFalse(element) || name == 'IMG') {
- return {name: name, index: findIndex(name, element)};
- }
- if (selection.tridentSel) {
- return selection.tridentSel.getBookmark(type);
- }
- element = findAdjacentContentEditableFalseElm(rng);
- if (element) {
- name = element.tagName;
- return {name: name, index: findIndex(name, element)};
- }
- return getLocation(rng);
- }
- if (type == 3) {
- rng = selection.getRng();
- return {
- start: CaretBookmark.create(dom.getRoot(), CaretPosition.fromRangeStart(rng)),
- end: CaretBookmark.create(dom.getRoot(), CaretPosition.fromRangeEnd(rng))
- };
- }
- // Handle simple range
- if (type) {
- return {rng: selection.getRng()};
- }
- rng = selection.getRng();
- id = dom.uniqueId();
- collapsed = selection.isCollapsed();
- styles = 'overflow:hidden;line-height:0px';
- // Explorer method
- if (rng.duplicate || rng.item) {
- // Text selection
- if (!rng.item) {
- rng2 = rng.duplicate();
- try {
- // Insert start marker
- rng.collapse();
- rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
- // Insert end marker
- if (!collapsed) {
- rng2.collapse(false);
- // Detect the empty space after block elements in IE and move the
- // end back one character <p></p>] becomes <p>]</p>
- rng.moveToElementText(rng2.parentElement());
- if (rng.compareEndPoints('StartToEnd', rng2) === 0) {
- rng2.move('character', -1);
- }
- rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
- }
- } catch (ex) {
- // IE might throw unspecified error so lets ignore it
- return null;
- }
- } else {
- // Control selection
- element = rng.item(0);
- name = element.nodeName;
- return {name: name, index: findIndex(name, element)};
- }
- } else {
- element = selection.getNode();
- name = element.nodeName;
- if (name == 'IMG') {
- return {name: name, index: findIndex(name, element)};
- }
- // W3C method
- rng2 = normalizeTableCellSelection(rng.cloneRange());
- // Insert end marker
- if (!collapsed) {
- rng2.collapse(false);
- rng2.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_end', style: styles}, chr));
- }
- rng = normalizeTableCellSelection(rng);
- rng.collapse(true);
- rng.insertNode(dom.create('span', {'data-mce-type': "bookmark", id: id + '_start', style: styles}, chr));
- }
- selection.moveToBookmark({id: id, keep: 1});
- return {id: id};
- };
- /**
- * Restores the selection to the specified bookmark.
- *
- * @method moveToBookmark
- * @param {Object} bookmark Bookmark to restore selection from.
- * @return {Boolean} true/false if it was successful or not.
- * @example
- * // Stores a bookmark of the current selection
- * var bm = tinymce.activeEditor.selection.getBookmark();
- *
- * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
- *
- * // Restore the selection bookmark
- * tinymce.activeEditor.selection.moveToBookmark(bm);
- */
- this.moveToBookmark = function(bookmark) {
- var rng, root, startContainer, endContainer, startOffset, endOffset;
- function setEndPoint(start) {
- var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
- if (point) {
- offset = point[0];
- // Find container node
- for (node = root, i = point.length - 1; i >= 1; i--) {
- children = node.childNodes;
- if (point[i] > children.length - 1) {
- return;
- }
- node = children[point[i]];
- }
- // Move text offset to best suitable location
- if (node.nodeType === 3) {
- offset = Math.min(point[0], node.nodeValue.length);
- }
- // Move element offset to best suitable location
- if (node.nodeType === 1) {
- offset = Math.min(point[0], node.childNodes.length);
- }
- // Set offset within container node
- if (start) {
- rng.setStart(node, offset);
- } else {
- rng.setEnd(node, offset);
- }
- }
- return true;
- }
- function restoreEndPoint(suffix) {
- var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
- if (marker) {
- node = marker.parentNode;
- if (suffix == 'start') {
- if (!keep) {
- idx = dom.nodeIndex(marker);
- } else {
- node = marker.firstChild;
- idx = 1;
- }
- startContainer = endContainer = node;
- startOffset = endOffset = idx;
- } else {
- if (!keep) {
- idx = dom.nodeIndex(marker);
- } else {
- node = marker.firstChild;
- idx = 1;
- }
- endContainer = node;
- endOffset = idx;
- }
- if (!keep) {
- prev = marker.previousSibling;
- next = marker.nextSibling;
- // Remove all marker text nodes
- Tools.each(Tools.grep(marker.childNodes), function(node) {
- if (node.nodeType == 3) {
- node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
- }
- });
- // Remove marker but keep children if for example contents where inserted into the marker
- // Also remove duplicated instances of the marker for example by a
- // split operation or by WebKit auto split on paste feature
- while ((marker = dom.get(bookmark.id + '_' + suffix))) {
- dom.remove(marker, 1);
- }
- // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
- // and we are sniffing since adding a lot of detection code for a browser with 3% of the market
- // isn't worth the effort. Sorry, Opera but it's just a fact
- if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !Env.opera) {
- idx = prev.nodeValue.length;
- prev.appendData(next.nodeValue);
- dom.remove(next);
- if (suffix == 'start') {
- startContainer = endContainer = prev;
- startOffset = endOffset = idx;
- } else {
- endContainer = prev;
- endOffset = idx;
- }
- }
- }
- }
- }
- function addBogus(node) {
- // Adds a bogus BR element for empty block elements
- if (dom.isBlock(node) && !node.innerHTML && !Env.ie) {
- node.innerHTML = '<br data-mce-bogus="1" />';
- }
- return node;
- }
- function resolveCaretPositionBookmark() {
- var rng, pos;
- rng = dom.createRng();
- pos = CaretBookmark.resolve(dom.getRoot(), bookmark.start);
- rng.setStart(pos.container(), pos.offset());
- pos = CaretBookmark.resolve(dom.getRoot(), bookmark.end);
- rng.setEnd(pos.container(), pos.offset());
- return rng;
- }
- if (bookmark) {
- if (Tools.isArray(bookmark.start)) {
- rng = dom.createRng();
- root = dom.getRoot();
- if (selection.tridentSel) {
- return selection.tridentSel.moveToBookmark(bookmark);
- }
- if (setEndPoint(true) && setEndPoint()) {
- selection.setRng(rng);
- }
- } else if (typeof bookmark.start == 'string') {
- selection.setRng(resolveCaretPositionBookmark(bookmark));
- } else if (bookmark.id) {
- // Restore start/end points
- restoreEndPoint('start');
- restoreEndPoint('end');
- if (startContainer) {
- rng = dom.createRng();
- rng.setStart(addBogus(startContainer), startOffset);
- rng.setEnd(addBogus(endContainer), endOffset);
- selection.setRng(rng);
- }
- } else if (bookmark.name) {
- selection.select(dom.select(bookmark.name)[bookmark.index]);
- } else if (bookmark.rng) {
- selection.setRng(bookmark.rng);
- }
- }
- };
- }
- /**
- * Returns true/false if the specified node is a bookmark node or not.
- *
- * @static
- * @method isBookmarkNode
- * @param {DOMNode} node DOM Node to check if it's a bookmark node or not.
- * @return {Boolean} true/false if the node is a bookmark node or not.
- */
- BookmarkManager.isBookmarkNode = function(node) {
- return node && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
- };
- return BookmarkManager;
- });
- // Included from: js/tinymce/classes/dom/Selection.js
- /**
- * Selection.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles text and control selection it's an crossbrowser utility class.
- * Consult the TinyMCE Wiki API for more details and examples on how to use this class.
- *
- * @class tinymce.dom.Selection
- * @example
- * // Getting the currently selected node for the active editor
- * alert(tinymce.activeEditor.selection.getNode().nodeName);
- */
- define("tinymce/dom/Selection", [
- "tinymce/dom/TreeWalker",
- "tinymce/dom/TridentSelection",
- "tinymce/dom/ControlSelection",
- "tinymce/dom/RangeUtils",
- "tinymce/dom/BookmarkManager",
- "tinymce/dom/NodeType",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/caret/CaretPosition"
- ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, BookmarkManager, NodeType, Env, Tools, CaretPosition) {
- var each = Tools.each, trim = Tools.trim;
- var isIE = Env.ie;
- /**
- * Constructs a new selection instance.
- *
- * @constructor
- * @method Selection
- * @param {tinymce.dom.DOMUtils} dom DOMUtils object reference.
- * @param {Window} win Window to bind the selection object to.
- * @param {tinymce.Editor} editor Editor instance of the selection.
- * @param {tinymce.dom.Serializer} serializer DOM serialization class to use for getContent.
- */
- function Selection(dom, win, serializer, editor) {
- var self = this;
- self.dom = dom;
- self.win = win;
- self.serializer = serializer;
- self.editor = editor;
- self.bookmarkManager = new BookmarkManager(self);
- self.controlSelection = new ControlSelection(self, editor);
- // No W3C Range support
- if (!self.win.getSelection) {
- self.tridentSel = new TridentSelection(self);
- }
- }
- Selection.prototype = {
- /**
- * Move the selection cursor range to the specified node and offset.
- * If there is no node specified it will move it to the first suitable location within the body.
- *
- * @method setCursorLocation
- * @param {Node} node Optional node to put the cursor in.
- * @param {Number} offset Optional offset from the start of the node to put the cursor at.
- */
- setCursorLocation: function(node, offset) {
- var self = this, rng = self.dom.createRng();
- if (!node) {
- self._moveEndPoint(rng, self.editor.getBody(), true);
- self.setRng(rng);
- } else {
- rng.setStart(node, offset);
- rng.setEnd(node, offset);
- self.setRng(rng);
- self.collapse(false);
- }
- },
- /**
- * Returns the selected contents using the DOM serializer passed in to this class.
- *
- * @method getContent
- * @param {Object} args Optional settings class with for example output format text or html.
- * @return {String} Selected contents in for example HTML format.
- * @example
- * // Alerts the currently selected contents
- * alert(tinymce.activeEditor.selection.getContent());
- *
- * // Alerts the currently selected contents as plain text
- * alert(tinymce.activeEditor.selection.getContent({format: 'text'}));
- */
- getContent: function(args) {
- var self = this, rng = self.getRng(), tmpElm = self.dom.create("body");
- var se = self.getSel(), whiteSpaceBefore, whiteSpaceAfter, fragment;
- args = args || {};
- whiteSpaceBefore = whiteSpaceAfter = '';
- args.get = true;
- args.format = args.format || 'html';
- args.selection = true;
- self.editor.fire('BeforeGetContent', args);
- if (args.format == 'text') {
- return self.isCollapsed() ? '' : (rng.text || (se.toString ? se.toString() : ''));
- }
- if (rng.cloneContents) {
- fragment = rng.cloneContents();
- if (fragment) {
- tmpElm.appendChild(fragment);
- }
- } else if (rng.item !== undefined || rng.htmlText !== undefined) {
- // IE will produce invalid markup if elements are present that
- // it doesn't understand like custom elements or HTML5 elements.
- // Adding a BR in front of the contents and then remoiving it seems to fix it though.
- tmpElm.innerHTML = '<br>' + (rng.item ? rng.item(0).outerHTML : rng.htmlText);
- tmpElm.removeChild(tmpElm.firstChild);
- } else {
- tmpElm.innerHTML = rng.toString();
- }
- // Keep whitespace before and after
- if (/^\s/.test(tmpElm.innerHTML)) {
- whiteSpaceBefore = ' ';
- }
- if (/\s+$/.test(tmpElm.innerHTML)) {
- whiteSpaceAfter = ' ';
- }
- args.getInner = true;
- args.content = self.isCollapsed() ? '' : whiteSpaceBefore + self.serializer.serialize(tmpElm, args) + whiteSpaceAfter;
- self.editor.fire('GetContent', args);
- return args.content;
- },
- /**
- * Sets the current selection to the specified content. If any contents is selected it will be replaced
- * with the contents passed in to this function. If there is no selection the contents will be inserted
- * where the caret is placed in the editor/page.
- *
- * @method setContent
- * @param {String} content HTML contents to set could also be other formats depending on settings.
- * @param {Object} args Optional settings object with for example data format.
- * @example
- * // Inserts some HTML contents at the current selection
- * tinymce.activeEditor.selection.setContent('<strong>Some contents</strong>');
- */
- setContent: function(content, args) {
- var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
- args = args || {format: 'html'};
- args.set = true;
- args.selection = true;
- args.content = content;
- // Dispatch before set content event
- if (!args.no_events) {
- self.editor.fire('BeforeSetContent', args);
- }
- content = args.content;
- if (rng.insertNode) {
- // Make caret marker since insertNode places the caret in the beginning of text after insert
- content += '<span id="__caret">_</span>';
- // Delete and insert new node
- if (rng.startContainer == doc && rng.endContainer == doc) {
- // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
- doc.body.innerHTML = content;
- } else {
- rng.deleteContents();
- if (doc.body.childNodes.length === 0) {
- doc.body.innerHTML = content;
- } else {
- // createContextualFragment doesn't exists in IE 9 DOMRanges
- if (rng.createContextualFragment) {
- rng.insertNode(rng.createContextualFragment(content));
- } else {
- // Fake createContextualFragment call in IE 9
- frag = doc.createDocumentFragment();
- temp = doc.createElement('div');
- frag.appendChild(temp);
- temp.outerHTML = content;
- rng.insertNode(frag);
- }
- }
- }
- // Move to caret marker
- caretNode = self.dom.get('__caret');
- // Make sure we wrap it compleatly, Opera fails with a simple select call
- rng = doc.createRange();
- rng.setStartBefore(caretNode);
- rng.setEndBefore(caretNode);
- self.setRng(rng);
- // Remove the caret position
- self.dom.remove('__caret');
- try {
- self.setRng(rng);
- } catch (ex) {
- // Might fail on Opera for some odd reason
- }
- } else {
- if (rng.item) {
- // Delete content and get caret text selection
- doc.execCommand('Delete', false, null);
- rng = self.getRng();
- }
- // Explorer removes spaces from the beginning of pasted contents
- if (/^\s+/.test(content)) {
- rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
- self.dom.remove('__mce_tmp');
- } else {
- rng.pasteHTML(content);
- }
- }
- // Dispatch set content event
- if (!args.no_events) {
- self.editor.fire('SetContent', args);
- }
- },
- /**
- * Returns the start element of a selection range. If the start is in a text
- * node the parent element will be returned.
- *
- * @method getStart
- * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
- * @return {Element} Start element of selection range.
- */
- getStart: function(real) {
- var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
- if (rng.duplicate || rng.item) {
- // Control selection, return first item
- if (rng.item) {
- return rng.item(0);
- }
- // Get start element
- checkRng = rng.duplicate();
- checkRng.collapse(1);
- startElement = checkRng.parentElement();
- if (startElement.ownerDocument !== self.dom.doc) {
- startElement = self.dom.getRoot();
- }
- // Check if range parent is inside the start element, then return the inner parent element
- // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
- parentElement = node = rng.parentElement();
- while ((node = node.parentNode)) {
- if (node == startElement) {
- startElement = parentElement;
- break;
- }
- }
- return startElement;
- }
- startElement = rng.startContainer;
- if (startElement.nodeType == 1 && startElement.hasChildNodes()) {
- if (!real || !rng.collapsed) {
- startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
- }
- }
- if (startElement && startElement.nodeType == 3) {
- return startElement.parentNode;
- }
- return startElement;
- },
- /**
- * Returns the end element of a selection range. If the end is in a text
- * node the parent element will be returned.
- *
- * @method getEnd
- * @param {Boolean} real Optional state to get the real parent when the selection is collapsed not the closest element.
- * @return {Element} End element of selection range.
- */
- getEnd: function(real) {
- var self = this, rng = self.getRng(), endElement, endOffset;
- if (rng.duplicate || rng.item) {
- if (rng.item) {
- return rng.item(0);
- }
- rng = rng.duplicate();
- rng.collapse(0);
- endElement = rng.parentElement();
- if (endElement.ownerDocument !== self.dom.doc) {
- endElement = self.dom.getRoot();
- }
- if (endElement && endElement.nodeName == 'BODY') {
- return endElement.lastChild || endElement;
- }
- return endElement;
- }
- endElement = rng.endContainer;
- endOffset = rng.endOffset;
- if (endElement.nodeType == 1 && endElement.hasChildNodes()) {
- if (!real || !rng.collapsed) {
- endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
- }
- }
- if (endElement && endElement.nodeType == 3) {
- return endElement.parentNode;
- }
- return endElement;
- },
- /**
- * Returns a bookmark location for the current selection. This bookmark object
- * can then be used to restore the selection after some content modification to the document.
- *
- * @method getBookmark
- * @param {Number} type Optional state if the bookmark should be simple or not. Default is complex.
- * @param {Boolean} normalized Optional state that enables you to get a position that it would be after normalization.
- * @return {Object} Bookmark object, use moveToBookmark with this object to restore the selection.
- * @example
- * // Stores a bookmark of the current selection
- * var bm = tinymce.activeEditor.selection.getBookmark();
- *
- * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
- *
- * // Restore the selection bookmark
- * tinymce.activeEditor.selection.moveToBookmark(bm);
- */
- getBookmark: function(type, normalized) {
- return this.bookmarkManager.getBookmark(type, normalized);
- },
- /**
- * Restores the selection to the specified bookmark.
- *
- * @method moveToBookmark
- * @param {Object} bookmark Bookmark to restore selection from.
- * @return {Boolean} true/false if it was successful or not.
- * @example
- * // Stores a bookmark of the current selection
- * var bm = tinymce.activeEditor.selection.getBookmark();
- *
- * tinymce.activeEditor.setContent(tinymce.activeEditor.getContent() + 'Some new content');
- *
- * // Restore the selection bookmark
- * tinymce.activeEditor.selection.moveToBookmark(bm);
- */
- moveToBookmark: function(bookmark) {
- return this.bookmarkManager.moveToBookmark(bookmark);
- },
- /**
- * Selects the specified element. This will place the start and end of the selection range around the element.
- *
- * @method select
- * @param {Element} node HTML DOM element to select.
- * @param {Boolean} content Optional bool state if the contents should be selected or not on non IE browser.
- * @return {Element} Selected element the same element as the one that got passed in.
- * @example
- * // Select the first paragraph in the active editor
- * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
- */
- select: function(node, content) {
- var self = this, dom = self.dom, rng = dom.createRng(), idx;
- // Clear stored range set by FocusManager
- self.lastFocusBookmark = null;
- if (node) {
- if (!content && self.controlSelection.controlSelect(node)) {
- return;
- }
- idx = dom.nodeIndex(node);
- rng.setStart(node.parentNode, idx);
- rng.setEnd(node.parentNode, idx + 1);
- // Find first/last text node or BR element
- if (content) {
- self._moveEndPoint(rng, node, true);
- self._moveEndPoint(rng, node);
- }
- self.setRng(rng);
- }
- return node;
- },
- /**
- * Returns true/false if the selection range is collapsed or not. Collapsed means if it's a caret or a larger selection.
- *
- * @method isCollapsed
- * @return {Boolean} true/false state if the selection range is collapsed or not.
- * Collapsed means if it's a caret or a larger selection.
- */
- isCollapsed: function() {
- var self = this, rng = self.getRng(), sel = self.getSel();
- if (!rng || rng.item) {
- return false;
- }
- if (rng.compareEndPoints) {
- return rng.compareEndPoints('StartToEnd', rng) === 0;
- }
- return !sel || rng.collapsed;
- },
- /**
- * Collapse the selection to start or end of range.
- *
- * @method collapse
- * @param {Boolean} toStart Optional boolean state if to collapse to end or not. Defaults to false.
- */
- collapse: function(toStart) {
- var self = this, rng = self.getRng(), node;
- // Control range on IE
- if (rng.item) {
- node = rng.item(0);
- rng = self.win.document.body.createTextRange();
- rng.moveToElementText(node);
- }
- rng.collapse(!!toStart);
- self.setRng(rng);
- },
- /**
- * Returns the browsers internal selection object.
- *
- * @method getSel
- * @return {Selection} Internal browser selection object.
- */
- getSel: function() {
- var win = this.win;
- return win.getSelection ? win.getSelection() : win.document.selection;
- },
- /**
- * Returns the browsers internal range object.
- *
- * @method getRng
- * @param {Boolean} w3c Forces a compatible W3C range on IE.
- * @return {Range} Internal browser range object.
- * @see http://www.quirksmode.org/dom/range_intro.html
- * @see http://www.dotvoid.com/2001/03/using-the-range-object-in-mozilla/
- */
- getRng: function(w3c) {
- var self = this, selection, rng, elm, doc, ieRng, evt;
- function tryCompareBoundaryPoints(how, sourceRange, destinationRange) {
- try {
- return sourceRange.compareBoundaryPoints(how, destinationRange);
- } catch (ex) {
- // Gecko throws wrong document exception if the range points
- // to nodes that where removed from the dom #6690
- // Browsers should mutate existing DOMRange instances so that they always point
- // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
- // For performance reasons just return -1
- return -1;
- }
- }
- if (!self.win) {
- return null;
- }
- doc = self.win.document;
- // Use last rng passed from FocusManager if it's available this enables
- // calls to editor.selection.getStart() to work when caret focus is lost on IE
- if (!w3c && self.lastFocusBookmark) {
- var bookmark = self.lastFocusBookmark;
- // Convert bookmark to range IE 11 fix
- if (bookmark.startContainer) {
- rng = doc.createRange();
- rng.setStart(bookmark.startContainer, bookmark.startOffset);
- rng.setEnd(bookmark.endContainer, bookmark.endOffset);
- } else {
- rng = bookmark;
- }
- return rng;
- }
- // Found tridentSel object then we need to use that one
- if (w3c && self.tridentSel) {
- return self.tridentSel.getRangeAt(0);
- }
- try {
- if ((selection = self.getSel())) {
- if (selection.rangeCount > 0) {
- rng = selection.getRangeAt(0);
- } else {
- rng = selection.createRange ? selection.createRange() : doc.createRange();
- }
- }
- } catch (ex) {
- // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
- }
- evt = self.editor.fire('GetSelectionRange', {range: rng});
- if (evt.range !== rng) {
- return evt.range;
- }
- // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
- // IE 11 doesn't support the selection object so we check for that as well
- if (isIE && rng && rng.setStart && doc.selection) {
- try {
- // IE will sometimes throw an exception here
- ieRng = doc.selection.createRange();
- } catch (ex) {
- // Ignore
- }
- if (ieRng && ieRng.item) {
- elm = ieRng.item(0);
- rng = doc.createRange();
- rng.setStartBefore(elm);
- rng.setEndAfter(elm);
- }
- }
- // No range found then create an empty one
- // This can occur when the editor is placed in a hidden container element on Gecko
- // Or on IE when there was an exception
- if (!rng) {
- rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
- }
- // If range is at start of document then move it to start of body
- if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
- elm = self.dom.getRoot();
- rng.setStart(elm, 0);
- rng.setEnd(elm, 0);
- }
- if (self.selectedRange && self.explicitRange) {
- if (tryCompareBoundaryPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
- tryCompareBoundaryPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
- // Safari, Opera and Chrome only ever select text which causes the range to change.
- // This lets us use the originally set range if the selection hasn't been changed by the user.
- rng = self.explicitRange;
- } else {
- self.selectedRange = null;
- self.explicitRange = null;
- }
- }
- return rng;
- },
- /**
- * Changes the selection to the specified DOM range.
- *
- * @method setRng
- * @param {Range} rng Range to select.
- * @param {Boolean} forward Optional boolean if the selection is forwards or backwards.
- */
- setRng: function(rng, forward) {
- var self = this, sel, node, evt;
- if (!rng) {
- return;
- }
- // Is IE specific range
- if (rng.select) {
- self.explicitRange = null;
- try {
- rng.select();
- } catch (ex) {
- // Needed for some odd IE bug #1843306
- }
- return;
- }
- if (!self.tridentSel) {
- sel = self.getSel();
- evt = self.editor.fire('SetSelectionRange', {range: rng});
- rng = evt.range;
- if (sel) {
- self.explicitRange = rng;
- try {
- sel.removeAllRanges();
- sel.addRange(rng);
- } catch (ex) {
- // IE might throw errors here if the editor is within a hidden container and selection is changed
- }
- // Forward is set to false and we have an extend function
- if (forward === false && sel.extend) {
- sel.collapse(rng.endContainer, rng.endOffset);
- sel.extend(rng.startContainer, rng.startOffset);
- }
- // adding range isn't always successful so we need to check range count otherwise an exception can occur
- self.selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
- }
- // WebKit egde case selecting images works better using setBaseAndExtent
- if (!rng.collapsed && rng.startContainer == rng.endContainer && sel.setBaseAndExtent && !Env.ie) {
- if (rng.endOffset - rng.startOffset < 2) {
- if (rng.startContainer.hasChildNodes()) {
- node = rng.startContainer.childNodes[rng.startOffset];
- if (node && node.tagName == 'IMG') {
- self.getSel().setBaseAndExtent(node, 0, node, 1);
- }
- }
- }
- }
- } else {
- // Is W3C Range fake range on IE
- if (rng.cloneRange) {
- try {
- self.tridentSel.addRange(rng);
- } catch (ex) {
- //IE9 throws an error here if called before selection is placed in the editor
- }
- }
- }
- },
- /**
- * Sets the current selection to the specified DOM element.
- *
- * @method setNode
- * @param {Element} elm Element to set as the contents of the selection.
- * @return {Element} Returns the element that got passed in.
- * @example
- * // Inserts a DOM node at current selection/caret location
- * tinymce.activeEditor.selection.setNode(tinymce.activeEditor.dom.create('img', {src: 'some.gif', title: 'some title'}));
- */
- setNode: function(elm) {
- var self = this;
- self.setContent(self.dom.getOuterHTML(elm));
- return elm;
- },
- /**
- * Returns the currently selected element or the common ancestor element for both start and end of the selection.
- *
- * @method getNode
- * @return {Element} Currently selected element or common ancestor element.
- * @example
- * // Alerts the currently selected elements node name
- * alert(tinymce.activeEditor.selection.getNode().nodeName);
- */
- getNode: function() {
- var self = this, rng = self.getRng(), elm;
- var startContainer, endContainer, startOffset, endOffset, root = self.dom.getRoot();
- function skipEmptyTextNodes(node, forwards) {
- var orig = node;
- while (node && node.nodeType === 3 && node.length === 0) {
- node = forwards ? node.nextSibling : node.previousSibling;
- }
- return node || orig;
- }
- // Range maybe lost after the editor is made visible again
- if (!rng) {
- return root;
- }
- startContainer = rng.startContainer;
- endContainer = rng.endContainer;
- startOffset = rng.startOffset;
- endOffset = rng.endOffset;
- if (rng.setStart) {
- elm = rng.commonAncestorContainer;
- // Handle selection a image or other control like element such as anchors
- if (!rng.collapsed) {
- if (startContainer == endContainer) {
- if (endOffset - startOffset < 2) {
- if (startContainer.hasChildNodes()) {
- elm = startContainer.childNodes[startOffset];
- }
- }
- }
- // If the anchor node is a element instead of a text node then return this element
- //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
- // return sel.anchorNode.childNodes[sel.anchorOffset];
- // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
- // This happens when you double click an underlined word in FireFox.
- if (startContainer.nodeType === 3 && endContainer.nodeType === 3) {
- if (startContainer.length === startOffset) {
- startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
- } else {
- startContainer = startContainer.parentNode;
- }
- if (endOffset === 0) {
- endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
- } else {
- endContainer = endContainer.parentNode;
- }
- if (startContainer && startContainer === endContainer) {
- return startContainer;
- }
- }
- }
- if (elm && elm.nodeType == 3) {
- return elm.parentNode;
- }
- return elm;
- }
- elm = rng.item ? rng.item(0) : rng.parentElement();
- // IE 7 might return elements outside the iframe
- if (elm.ownerDocument !== self.win.document) {
- elm = root;
- }
- return elm;
- },
- getSelectedBlocks: function(startElm, endElm) {
- var self = this, dom = self.dom, node, root, selectedBlocks = [];
- root = dom.getRoot();
- startElm = dom.getParent(startElm || self.getStart(), dom.isBlock);
- endElm = dom.getParent(endElm || self.getEnd(), dom.isBlock);
- if (startElm && startElm != root) {
- selectedBlocks.push(startElm);
- }
- if (startElm && endElm && startElm != endElm) {
- node = startElm;
- var walker = new TreeWalker(startElm, root);
- while ((node = walker.next()) && node != endElm) {
- if (dom.isBlock(node)) {
- selectedBlocks.push(node);
- }
- }
- }
- if (endElm && startElm != endElm && endElm != root) {
- selectedBlocks.push(endElm);
- }
- return selectedBlocks;
- },
- isForward: function() {
- var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
- // No support for selection direction then always return true
- if (!sel || !sel.anchorNode || !sel.focusNode) {
- return true;
- }
- anchorRange = dom.createRng();
- anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
- anchorRange.collapse(true);
- focusRange = dom.createRng();
- focusRange.setStart(sel.focusNode, sel.focusOffset);
- focusRange.collapse(true);
- return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
- },
- normalize: function() {
- var self = this, rng = self.getRng();
- if (Env.range && new RangeUtils(self.dom).normalize(rng)) {
- self.setRng(rng, self.isForward());
- }
- return rng;
- },
- /**
- * Executes callback when the current selection starts/stops matching the specified selector. The current
- * state will be passed to the callback as it's first argument.
- *
- * @method selectorChanged
- * @param {String} selector CSS selector to check for.
- * @param {function} callback Callback with state and args when the selector is matches or not.
- */
- selectorChanged: function(selector, callback) {
- var self = this, currentSelectors;
- if (!self.selectorChangedData) {
- self.selectorChangedData = {};
- currentSelectors = {};
- self.editor.on('NodeChange', function(e) {
- var node = e.element, dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
- // Check for new matching selectors
- each(self.selectorChangedData, function(callbacks, selector) {
- each(parents, function(node) {
- if (dom.is(node, selector)) {
- if (!currentSelectors[selector]) {
- // Execute callbacks
- each(callbacks, function(callback) {
- callback(true, {node: node, selector: selector, parents: parents});
- });
- currentSelectors[selector] = callbacks;
- }
- matchedSelectors[selector] = callbacks;
- return false;
- }
- });
- });
- // Check if current selectors still match
- each(currentSelectors, function(callbacks, selector) {
- if (!matchedSelectors[selector]) {
- delete currentSelectors[selector];
- each(callbacks, function(callback) {
- callback(false, {node: node, selector: selector, parents: parents});
- });
- }
- });
- });
- }
- // Add selector listeners
- if (!self.selectorChangedData[selector]) {
- self.selectorChangedData[selector] = [];
- }
- self.selectorChangedData[selector].push(callback);
- return self;
- },
- getScrollContainer: function() {
- var scrollContainer, node = this.dom.getRoot();
- while (node && node.nodeName != 'BODY') {
- if (node.scrollHeight > node.clientHeight) {
- scrollContainer = node;
- break;
- }
- node = node.parentNode;
- }
- return scrollContainer;
- },
- scrollIntoView: function(elm, alignToTop) {
- var y, viewPort, self = this, dom = self.dom, root = dom.getRoot(), viewPortY, viewPortH, offsetY = 0;
- function getPos(elm) {
- var x = 0, y = 0;
- var offsetParent = elm;
- while (offsetParent && offsetParent.nodeType) {
- x += offsetParent.offsetLeft || 0;
- y += offsetParent.offsetTop || 0;
- offsetParent = offsetParent.offsetParent;
- }
- return {x: x, y: y};
- }
- if (!NodeType.isElement(elm)) {
- return;
- }
- if (alignToTop === false) {
- offsetY = elm.offsetHeight;
- }
- if (root.nodeName != 'BODY') {
- var scrollContainer = self.getScrollContainer();
- if (scrollContainer) {
- y = getPos(elm).y - getPos(scrollContainer).y + offsetY;
- viewPortH = scrollContainer.clientHeight;
- viewPortY = scrollContainer.scrollTop;
- if (y < viewPortY || y + 25 > viewPortY + viewPortH) {
- scrollContainer.scrollTop = y < viewPortY ? y : y - viewPortH + 25;
- }
- return;
- }
- }
- viewPort = dom.getViewPort(self.editor.getWin());
- y = dom.getPos(elm).y + offsetY;
- viewPortY = viewPort.y;
- viewPortH = viewPort.h;
- if (y < viewPort.y || y + 25 > viewPortY + viewPortH) {
- self.editor.getWin().scrollTo(0, y < viewPortY ? y : y - viewPortH + 25);
- }
- },
- placeCaretAt: function(clientX, clientY) {
- this.setRng(RangeUtils.getCaretRangeFromPoint(clientX, clientY, this.editor.getDoc()));
- },
- _moveEndPoint: function(rng, node, start) {
- var root = node, walker = new TreeWalker(node, root);
- var nonEmptyElementsMap = this.dom.schema.getNonEmptyElements();
- do {
- // Text node
- if (node.nodeType == 3 && trim(node.nodeValue).length !== 0) {
- if (start) {
- rng.setStart(node, 0);
- } else {
- rng.setEnd(node, node.nodeValue.length);
- }
- return;
- }
- // BR/IMG/INPUT elements but not table cells
- if (nonEmptyElementsMap[node.nodeName] && !/^(TD|TH)$/.test(node.nodeName)) {
- if (start) {
- rng.setStartBefore(node);
- } else {
- if (node.nodeName == 'BR') {
- rng.setEndBefore(node);
- } else {
- rng.setEndAfter(node);
- }
- }
- return;
- }
- // Found empty text block old IE can place the selection inside those
- if (Env.ie && Env.ie < 11 && this.dom.isBlock(node) && this.dom.isEmpty(node)) {
- if (start) {
- rng.setStart(node, 0);
- } else {
- rng.setEnd(node, 0);
- }
- return;
- }
- } while ((node = (start ? walker.next() : walker.prev())));
- // Failed to find any text node or other suitable location then move to the root of body
- if (root.nodeName == 'BODY') {
- if (start) {
- rng.setStart(root, 0);
- } else {
- rng.setEnd(root, root.childNodes.length);
- }
- }
- },
- getBoundingClientRect: function() {
- var rng = this.getRng();
- return rng.collapsed ? CaretPosition.fromRangeStart(rng).getClientRects()[0] : rng.getBoundingClientRect();
- },
- destroy: function() {
- this.win = null;
- this.controlSelection.destroy();
- }
- };
- return Selection;
- });
- // Included from: js/tinymce/classes/dom/ElementUtils.js
- /**
- * ElementUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility class for various element specific functions.
- *
- * @private
- * @class tinymce.dom.ElementUtils
- */
- define("tinymce/dom/ElementUtils", [
- "tinymce/dom/BookmarkManager",
- "tinymce/util/Tools"
- ], function(BookmarkManager, Tools) {
- var each = Tools.each;
- function ElementUtils(dom) {
- /**
- * Compares two nodes and checks if it's attributes and styles matches.
- * This doesn't compare classes as items since their order is significant.
- *
- * @method compare
- * @param {Node} node1 First node to compare with.
- * @param {Node} node2 Second node to compare with.
- * @return {boolean} True/false if the nodes are the same or not.
- */
- this.compare = function(node1, node2) {
- // Not the same name
- if (node1.nodeName != node2.nodeName) {
- return false;
- }
- /**
- * Returns all the nodes attributes excluding internal ones, styles and classes.
- *
- * @private
- * @param {Node} node Node to get attributes from.
- * @return {Object} Name/value object with attributes and attribute values.
- */
- function getAttribs(node) {
- var attribs = {};
- each(dom.getAttribs(node), function(attr) {
- var name = attr.nodeName.toLowerCase();
- // Don't compare internal attributes or style
- if (name.indexOf('_') !== 0 && name !== 'style' && name !== 'data-mce-style' && name != 'data-mce-fragment') {
- attribs[name] = dom.getAttrib(node, name);
- }
- });
- return attribs;
- }
- /**
- * Compares two objects checks if it's key + value exists in the other one.
- *
- * @private
- * @param {Object} obj1 First object to compare.
- * @param {Object} obj2 Second object to compare.
- * @return {boolean} True/false if the objects matches or not.
- */
- function compareObjects(obj1, obj2) {
- var value, name;
- for (name in obj1) {
- // Obj1 has item obj2 doesn't have
- if (obj1.hasOwnProperty(name)) {
- value = obj2[name];
- // Obj2 doesn't have obj1 item
- if (typeof value == "undefined") {
- return false;
- }
- // Obj2 item has a different value
- if (obj1[name] != value) {
- return false;
- }
- // Delete similar value
- delete obj2[name];
- }
- }
- // Check if obj 2 has something obj 1 doesn't have
- for (name in obj2) {
- // Obj2 has item obj1 doesn't have
- if (obj2.hasOwnProperty(name)) {
- return false;
- }
- }
- return true;
- }
- // Attribs are not the same
- if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
- return false;
- }
- // Styles are not the same
- if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
- return false;
- }
- return !BookmarkManager.isBookmarkNode(node1) && !BookmarkManager.isBookmarkNode(node2);
- };
- }
- return ElementUtils;
- });
- // Included from: js/tinymce/classes/fmt/Preview.js
- /**
- * Preview.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Internal class for generating previews styles for formats.
- *
- * Example:
- * Preview.getCssText(editor, 'bold');
- *
- * @private
- * @class tinymce.fmt.Preview
- */
- define("tinymce/fmt/Preview", [
- "tinymce/util/Tools"
- ], function(Tools) {
- var each = Tools.each;
- function getCssText(editor, format) {
- var name, previewElm, dom = editor.dom;
- var previewCss = '', parentFontSize, previewStyles;
- previewStyles = editor.settings.preview_styles;
- // No preview forced
- if (previewStyles === false) {
- return '';
- }
- // Default preview
- if (!previewStyles) {
- previewStyles = 'font-family font-size font-weight font-style text-decoration ' +
- 'text-transform color background-color border border-radius outline text-shadow';
- }
- // Removes any variables since these can't be previewed
- function removeVars(val) {
- return val.replace(/%(\w+)/g, '');
- }
- // Create block/inline element to use for preview
- if (typeof format == "string") {
- format = editor.formatter.get(format);
- if (!format) {
- return;
- }
- format = format[0];
- }
- name = format.block || format.inline || 'span';
- previewElm = dom.create(name);
- // Add format styles to preview element
- each(format.styles, function(value, name) {
- value = removeVars(value);
- if (value) {
- dom.setStyle(previewElm, name, value);
- }
- });
- // Add attributes to preview element
- each(format.attributes, function(value, name) {
- value = removeVars(value);
- if (value) {
- dom.setAttrib(previewElm, name, value);
- }
- });
- // Add classes to preview element
- each(format.classes, function(value) {
- value = removeVars(value);
- if (!dom.hasClass(previewElm, value)) {
- dom.addClass(previewElm, value);
- }
- });
- editor.fire('PreviewFormats');
- // Add the previewElm outside the visual area
- dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
- editor.getBody().appendChild(previewElm);
- // Get parent container font size so we can compute px values out of em/% for older IE:s
- parentFontSize = dom.getStyle(editor.getBody(), 'fontSize', true);
- parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize, 10) : 0;
- each(previewStyles.split(' '), function(name) {
- var value = dom.getStyle(previewElm, name, true);
- // If background is transparent then check if the body has a background color we can use
- if (name == 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
- value = dom.getStyle(editor.getBody(), name, true);
- // Ignore white since it's the default color, not the nicest fix
- // TODO: Fix this by detecting runtime style
- if (dom.toHex(value).toLowerCase() == '#ffffff') {
- return;
- }
- }
- if (name == 'color') {
- // Ignore black since it's the default color, not the nicest fix
- // TODO: Fix this by detecting runtime style
- if (dom.toHex(value).toLowerCase() == '#000000') {
- return;
- }
- }
- // Old IE won't calculate the font size so we need to do that manually
- if (name == 'font-size') {
- if (/em|%$/.test(value)) {
- if (parentFontSize === 0) {
- return;
- }
- // Convert font size from em/% to px
- value = parseFloat(value, 10) / (/%$/.test(value) ? 100 : 1);
- value = (value * parentFontSize) + 'px';
- }
- }
- if (name == "border" && value) {
- previewCss += 'padding:0 2px;';
- }
- previewCss += name + ':' + value + ';';
- });
- editor.fire('AfterPreviewFormats');
- //previewCss += 'line-height:normal';
- dom.remove(previewElm);
- return previewCss;
- }
- return {
- getCssText: getCssText
- };
- });
- // Included from: js/tinymce/classes/fmt/Hooks.js
- /**
- * Hooks.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Internal class for overriding formatting.
- *
- * @private
- * @class tinymce.fmt.Hooks
- */
- define("tinymce/fmt/Hooks", [
- "tinymce/util/Arr",
- "tinymce/dom/NodeType",
- "tinymce/dom/DomQuery"
- ], function(Arr, NodeType, $) {
- var postProcessHooks = [], filter = Arr.filter, each = Arr.each;
- function addPostProcessHook(name, hook) {
- var hooks = postProcessHooks[name];
- if (!hooks) {
- postProcessHooks[name] = hooks = [];
- }
- postProcessHooks[name].push(hook);
- }
- function postProcess(name, editor) {
- each(postProcessHooks[name], function(hook) {
- hook(editor);
- });
- }
- addPostProcessHook("pre", function(editor) {
- var rng = editor.selection.getRng(), isPre, blocks;
- function hasPreSibling(pre) {
- return isPre(pre.previousSibling) && Arr.indexOf(blocks, pre.previousSibling) != -1;
- }
- function joinPre(pre1, pre2) {
- $(pre2).remove();
- $(pre1).append('<br><br>').append(pre2.childNodes);
- }
- isPre = NodeType.matchNodeNames('pre');
- if (!rng.collapsed) {
- blocks = editor.selection.getSelectedBlocks();
- each(filter(filter(blocks, isPre), hasPreSibling), function(pre) {
- joinPre(pre.previousSibling, pre);
- });
- }
- });
- return {
- postProcess: postProcess
- };
- });
- // Included from: js/tinymce/classes/Formatter.js
- /**
- * Formatter.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Text formatter engine class. This class is used to apply formats like bold, italic, font size
- * etc to the current selection or specific nodes. This engine was built to replace the browser's
- * default formatting logic for execCommand due to its inconsistent and buggy behavior.
- *
- * @class tinymce.Formatter
- * @example
- * tinymce.activeEditor.formatter.register('mycustomformat', {
- * inline: 'span',
- * styles: {color: '#ff0000'}
- * });
- *
- * tinymce.activeEditor.formatter.apply('mycustomformat');
- */
- define("tinymce/Formatter", [
- "tinymce/dom/TreeWalker",
- "tinymce/dom/RangeUtils",
- "tinymce/dom/BookmarkManager",
- "tinymce/dom/ElementUtils",
- "tinymce/util/Tools",
- "tinymce/fmt/Preview",
- "tinymce/fmt/Hooks"
- ], function(TreeWalker, RangeUtils, BookmarkManager, ElementUtils, Tools, Preview, Hooks) {
- /**
- * Constructs a new formatter instance.
- *
- * @constructor Formatter
- * @param {tinymce.Editor} ed Editor instance to construct the formatter engine to.
- */
- return function(ed) {
- var formats = {},
- dom = ed.dom,
- selection = ed.selection,
- rangeUtils = new RangeUtils(dom),
- isValid = ed.schema.isValidChild,
- isBlock = dom.isBlock,
- forcedRootBlock = ed.settings.forced_root_block,
- nodeIndex = dom.nodeIndex,
- INVISIBLE_CHAR = '\uFEFF',
- MCE_ATTR_RE = /^(src|href|style)$/,
- FALSE = false,
- TRUE = true,
- formatChangeData,
- undef,
- getContentEditable = dom.getContentEditable,
- disableCaretContainer,
- markCaretContainersBogus,
- isBookmarkNode = BookmarkManager.isBookmarkNode;
- var each = Tools.each,
- grep = Tools.grep,
- walk = Tools.walk,
- extend = Tools.extend;
- function isTextBlock(name) {
- if (name.nodeType) {
- name = name.nodeName;
- }
- return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
- }
- function isTableCell(node) {
- return /^(TH|TD)$/.test(node.nodeName);
- }
- function isInlineBlock(node) {
- return node && /^(IMG)$/.test(node.nodeName);
- }
- function getParents(node, selector) {
- return dom.getParents(node, selector, dom.getRoot());
- }
- function isCaretNode(node) {
- return node.nodeType === 1 && node.id === '_mce_caret';
- }
- function defaultFormats() {
- register({
- valigntop: [
- {selector: 'td,th', styles: {'verticalAlign': 'top'}}
- ],
- valignmiddle: [
- {selector: 'td,th', styles: {'verticalAlign': 'middle'}}
- ],
- valignbottom: [
- {selector: 'td,th', styles: {'verticalAlign': 'bottom'}}
- ],
- alignleft: [
- {selector: 'figure.image', collapsed: false, classes: 'align-left', ceFalseOverride: true},
- {
- selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
- styles: {
- textAlign: 'left'
- },
- inherit: false,
- defaultBlock: 'div'
- },
- {selector: 'img,table', collapsed: false, styles: {'float': 'left'}}
- ],
- aligncenter: [
- {
- selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
- styles: {
- textAlign: 'center'
- },
- inherit: false,
- defaultBlock: 'div'
- },
- {selector: 'figure.image', collapsed: false, classes: 'align-center', ceFalseOverride: true},
- {selector: 'img', collapsed: false, styles: {display: 'block', marginLeft: 'auto', marginRight: 'auto'}},
- {selector: 'table', collapsed: false, styles: {marginLeft: 'auto', marginRight: 'auto'}}
- ],
- alignright: [
- {selector: 'figure.image', collapsed: false, classes: 'align-right', ceFalseOverride: true},
- {
- selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
- styles: {
- textAlign: 'right'
- },
- inherit: false,
- defaultBlock: 'div'
- },
- {selector: 'img,table', collapsed: false, styles: {'float': 'right'}}
- ],
- alignjustify: [
- {
- selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li',
- styles: {
- textAlign: 'justify'
- },
- inherit: false,
- defaultBlock: 'div'
- }
- ],
- bold: [
- {inline: 'strong', remove: 'all'},
- {inline: 'span', styles: {fontWeight: 'bold'}},
- {inline: 'b', remove: 'all'}
- ],
- italic: [
- {inline: 'em', remove: 'all'},
- {inline: 'span', styles: {fontStyle: 'italic'}},
- {inline: 'i', remove: 'all'}
- ],
- underline: [
- {inline: 'span', styles: {textDecoration: 'underline'}, exact: true},
- {inline: 'u', remove: 'all'}
- ],
- strikethrough: [
- {inline: 'span', styles: {textDecoration: 'line-through'}, exact: true},
- {inline: 'strike', remove: 'all'}
- ],
- forecolor: {inline: 'span', styles: {color: '%value'}, links: true, remove_similar: true},
- hilitecolor: {inline: 'span', styles: {backgroundColor: '%value'}, links: true, remove_similar: true},
- fontname: {inline: 'span', styles: {fontFamily: '%value'}},
- fontsize: {inline: 'span', styles: {fontSize: '%value'}},
- fontsize_class: {inline: 'span', attributes: {'class': '%value'}},
- blockquote: {block: 'blockquote', wrapper: 1, remove: 'all'},
- subscript: {inline: 'sub'},
- superscript: {inline: 'sup'},
- code: {inline: 'code'},
- link: {inline: 'a', selector: 'a', remove: 'all', split: true, deep: true,
- onmatch: function() {
- return true;
- },
- onformat: function(elm, fmt, vars) {
- each(vars, function(value, key) {
- dom.setAttrib(elm, key, value);
- });
- }
- },
- removeformat: [
- {
- selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins',
- remove: 'all',
- split: true,
- expand: false,
- block_expand: true,
- deep: true
- },
- {selector: 'span', attributes: ['style', 'class'], remove: 'empty', split: true, expand: false, deep: true},
- {selector: '*', attributes: ['style', 'class'], split: false, expand: false, deep: true}
- ]
- });
- // Register default block formats
- each('p h1 h2 h3 h4 h5 h6 div address pre div dt dd samp'.split(/\s/), function(name) {
- register(name, {block: name, remove: 'all'});
- });
- // Register user defined formats
- register(ed.settings.formats);
- }
- function addKeyboardShortcuts() {
- // Add some inline shortcuts
- ed.addShortcut('meta+b', 'bold_desc', 'Bold');
- ed.addShortcut('meta+i', 'italic_desc', 'Italic');
- ed.addShortcut('meta+u', 'underline_desc', 'Underline');
- // BlockFormat shortcuts keys
- for (var i = 1; i <= 6; i++) {
- ed.addShortcut('access+' + i, '', ['FormatBlock', false, 'h' + i]);
- }
- ed.addShortcut('access+7', '', ['FormatBlock', false, 'p']);
- ed.addShortcut('access+8', '', ['FormatBlock', false, 'div']);
- ed.addShortcut('access+9', '', ['FormatBlock', false, 'address']);
- }
- // Public functions
- /**
- * Returns the format by name or all formats if no name is specified.
- *
- * @method get
- * @param {String} name Optional name to retrieve by.
- * @return {Array/Object} Array/Object with all registered formats or a specific format.
- */
- function get(name) {
- return name ? formats[name] : formats;
- }
- /**
- * Registers a specific format by name.
- *
- * @method register
- * @param {Object/String} name Name of the format for example "bold".
- * @param {Object/Array} format Optional format object or array of format variants
- * can only be omitted if the first arg is an object.
- */
- function register(name, format) {
- if (name) {
- if (typeof name !== 'string') {
- each(name, function(format, name) {
- register(name, format);
- });
- } else {
- // Force format into array and add it to internal collection
- format = format.length ? format : [format];
- each(format, function(format) {
- // Set deep to false by default on selector formats this to avoid removing
- // alignment on images inside paragraphs when alignment is changed on paragraphs
- if (format.deep === undef) {
- format.deep = !format.selector;
- }
- // Default to true
- if (format.split === undef) {
- format.split = !format.selector || format.inline;
- }
- // Default to true
- if (format.remove === undef && format.selector && !format.inline) {
- format.remove = 'none';
- }
- // Mark format as a mixed format inline + block level
- if (format.selector && format.inline) {
- format.mixed = true;
- format.block_expand = true;
- }
- // Split classes if needed
- if (typeof format.classes === 'string') {
- format.classes = format.classes.split(/\s+/);
- }
- });
- formats[name] = format;
- }
- }
- }
- /**
- * Unregister a specific format by name.
- *
- * @method unregister
- * @param {String} name Name of the format for example "bold".
- */
- function unregister(name) {
- if (name && formats[name]) {
- delete formats[name];
- }
- return formats;
- }
- function matchesUnInheritedFormatSelector(node, name) {
- var formatList = get(name);
- if (formatList) {
- for (var i = 0; i < formatList.length; i++) {
- if (formatList[i].inherit === false && dom.is(node, formatList[i].selector)) {
- return true;
- }
- }
- }
- return false;
- }
- function getTextDecoration(node) {
- var decoration;
- ed.dom.getParent(node, function(n) {
- decoration = ed.dom.getStyle(n, 'text-decoration');
- return decoration && decoration !== 'none';
- });
- return decoration;
- }
- function processUnderlineAndColor(node) {
- var textDecoration;
- if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
- textDecoration = getTextDecoration(node.parentNode);
- if (ed.dom.getStyle(node, 'color') && textDecoration) {
- ed.dom.setStyle(node, 'text-decoration', textDecoration);
- } else if (ed.dom.getStyle(node, 'text-decoration') === textDecoration) {
- ed.dom.setStyle(node, 'text-decoration', null);
- }
- }
- }
- /**
- * Applies the specified format to the current selection or specified node.
- *
- * @method apply
- * @param {String} name Name of format to apply.
- * @param {Object} vars Optional list of variables to replace within format before applying it.
- * @param {Node} node Optional node to apply the format to defaults to current selection.
- */
- function apply(name, vars, node) {
- var formatList = get(name), format = formatList[0], bookmark, rng, isCollapsed = !node && selection.isCollapsed();
- function setElementFormat(elm, fmt) {
- fmt = fmt || format;
- if (elm) {
- if (fmt.onformat) {
- fmt.onformat(elm, fmt, vars, node);
- }
- each(fmt.styles, function(value, name) {
- dom.setStyle(elm, name, replaceVars(value, vars));
- });
- // Needed for the WebKit span spam bug
- // TODO: Remove this once WebKit/Blink fixes this
- if (fmt.styles) {
- var styleVal = dom.getAttrib(elm, 'style');
- if (styleVal) {
- elm.setAttribute('data-mce-style', styleVal);
- }
- }
- each(fmt.attributes, function(value, name) {
- dom.setAttrib(elm, name, replaceVars(value, vars));
- });
- each(fmt.classes, function(value) {
- value = replaceVars(value, vars);
- if (!dom.hasClass(elm, value)) {
- dom.addClass(elm, value);
- }
- });
- }
- }
- // This converts: <p>[a</p><p>]b</p> -> <p>[a]</p><p>b</p>
- function adjustSelectionToVisibleSelection() {
- function findSelectionEnd(start, end) {
- var walker = new TreeWalker(end);
- for (node = walker.prev2(); node; node = walker.prev2()) {
- if (node.nodeType == 3 && node.data.length > 0) {
- return node;
- }
- if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
- return node;
- }
- }
- }
- // Adjust selection so that a end container with a end offset of zero is not included in the selection
- // as this isn't visible to the user.
- var rng = ed.selection.getRng();
- var start = rng.startContainer;
- var end = rng.endContainer;
- if (start != end && rng.endOffset === 0) {
- var newEnd = findSelectionEnd(start, end);
- var endOffset = newEnd.nodeType == 3 ? newEnd.data.length : newEnd.childNodes.length;
- rng.setEnd(newEnd, endOffset);
- }
- return rng;
- }
- function applyRngStyle(rng, bookmark, node_specific) {
- var newWrappers = [], wrapName, wrapElm, contentEditable = true;
- // Setup wrapper element
- wrapName = format.inline || format.block;
- wrapElm = dom.create(wrapName);
- setElementFormat(wrapElm);
- rangeUtils.walk(rng, function(nodes) {
- var currentWrapElm;
- /**
- * Process a list of nodes wrap them.
- */
- function process(node) {
- var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
- lastContentEditable = contentEditable;
- nodeName = node.nodeName.toLowerCase();
- parentName = node.parentNode.nodeName.toLowerCase();
- // Node has a contentEditable value
- if (node.nodeType === 1 && getContentEditable(node)) {
- lastContentEditable = contentEditable;
- contentEditable = getContentEditable(node) === "true";
- hasContentEditableState = true; // We don't want to wrap the container only it's children
- }
- // Stop wrapping on br elements
- if (isEq(nodeName, 'br')) {
- currentWrapElm = 0;
- // Remove any br elements when we wrap things
- if (format.block) {
- dom.remove(node);
- }
- return;
- }
- // If node is wrapper type
- if (format.wrapper && matchNode(node, name, vars)) {
- currentWrapElm = 0;
- return;
- }
- // Can we rename the block
- // TODO: Break this if up, too complex
- if (contentEditable && !hasContentEditableState && format.block &&
- !format.wrapper && isTextBlock(nodeName) && isValid(parentName, wrapName)) {
- node = dom.rename(node, wrapName);
- setElementFormat(node);
- newWrappers.push(node);
- currentWrapElm = 0;
- return;
- }
- // Handle selector patterns
- if (format.selector) {
- // Look for matching formats
- each(formatList, function(format) {
- // Check collapsed state if it exists
- if ('collapsed' in format && format.collapsed !== isCollapsed) {
- return;
- }
- if (dom.is(node, format.selector) && !isCaretNode(node)) {
- setElementFormat(node, format);
- found = true;
- return false;
- }
- });
- // Continue processing if a selector match wasn't found and a inline element is defined
- if (!format.inline || found) {
- currentWrapElm = 0;
- return;
- }
- }
- // Is it valid to wrap this item
- // TODO: Break this if up, too complex
- if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
- !(!node_specific && node.nodeType === 3 &&
- node.nodeValue.length === 1 &&
- node.nodeValue.charCodeAt(0) === 65279) &&
- !isCaretNode(node) &&
- (!format.inline || !isBlock(node))) {
- // Start wrapping
- if (!currentWrapElm) {
- // Wrap the node
- currentWrapElm = dom.clone(wrapElm, FALSE);
- node.parentNode.insertBefore(currentWrapElm, node);
- newWrappers.push(currentWrapElm);
- }
- currentWrapElm.appendChild(node);
- } else {
- // Start a new wrapper for possible children
- currentWrapElm = 0;
- each(grep(node.childNodes), process);
- if (hasContentEditableState) {
- contentEditable = lastContentEditable; // Restore last contentEditable state from stack
- }
- // End the last wrapper
- currentWrapElm = 0;
- }
- }
- // Process siblings from range
- each(nodes, process);
- });
- // Apply formats to links as well to get the color of the underline to change as well
- if (format.links === true) {
- each(newWrappers, function(node) {
- function process(node) {
- if (node.nodeName === 'A') {
- setElementFormat(node, format);
- }
- each(grep(node.childNodes), process);
- }
- process(node);
- });
- }
- // Cleanup
- each(newWrappers, function(node) {
- var childCount;
- function getChildCount(node) {
- var count = 0;
- each(node.childNodes, function(node) {
- if (!isWhiteSpaceNode(node) && !isBookmarkNode(node)) {
- count++;
- }
- });
- return count;
- }
- function mergeStyles(node) {
- var child, clone;
- each(node.childNodes, function(node) {
- if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
- child = node;
- return FALSE; // break loop
- }
- });
- // If child was found and of the same type as the current node
- if (child && !isBookmarkNode(child) && matchName(child, format)) {
- clone = dom.clone(child, FALSE);
- setElementFormat(clone);
- dom.replace(clone, node, TRUE);
- dom.remove(child, 1);
- }
- return clone || node;
- }
- childCount = getChildCount(node);
- // Remove empty nodes but only if there is multiple wrappers and they are not block
- // elements so never remove single <h1></h1> since that would remove the
- // current empty block element where the caret is at
- if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
- dom.remove(node, 1);
- return;
- }
- if (format.inline || format.wrapper) {
- // Merges the current node with it's children of similar type to reduce the number of elements
- if (!format.exact && childCount === 1) {
- node = mergeStyles(node);
- }
- // Remove/merge children
- each(formatList, function(format) {
- // Merge all children of similar type will move styles from child to parent
- // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
- // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
- each(dom.select(format.inline, node), function(child) {
- if (isBookmarkNode(child)) {
- return;
- }
- removeFormat(format, vars, child, format.exact ? child : null);
- });
- });
- // Remove child if direct parent is of same type
- if (matchNode(node.parentNode, name, vars)) {
- dom.remove(node, 1);
- node = 0;
- return TRUE;
- }
- // Look for parent with similar style format
- if (format.merge_with_parents) {
- dom.getParent(node.parentNode, function(parent) {
- if (matchNode(parent, name, vars)) {
- dom.remove(node, 1);
- node = 0;
- return TRUE;
- }
- });
- }
- // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
- if (node && format.merge_siblings !== false) {
- node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
- node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
- }
- }
- });
- }
- if (getContentEditable(selection.getNode()) === "false") {
- node = selection.getNode();
- for (var i = 0, l = formatList.length; i < l; i++) {
- if (formatList[i].ceFalseOverride && dom.is(node, formatList[i].selector)) {
- setElementFormat(node, formatList[i]);
- return;
- }
- }
- return;
- }
- if (format) {
- if (node) {
- if (node.nodeType) {
- rng = dom.createRng();
- rng.setStartBefore(node);
- rng.setEndAfter(node);
- applyRngStyle(expandRng(rng, formatList), null, true);
- } else {
- applyRngStyle(node, null, true);
- }
- } else {
- if (!isCollapsed || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
- // Obtain selection node before selection is unselected by applyRngStyle()
- var curSelNode = ed.selection.getNode();
- // If the formats have a default block and we can't find a parent block then
- // start wrapping it with a DIV this is for forced_root_blocks: false
- // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
- if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
- apply(formatList[0].defaultBlock);
- }
- // Apply formatting to selection
- ed.selection.setRng(adjustSelectionToVisibleSelection());
- bookmark = selection.getBookmark();
- applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
- // Colored nodes should be underlined so that the color of the underline matches the text color.
- if (format.styles && (format.styles.color || format.styles.textDecoration)) {
- walk(curSelNode, processUnderlineAndColor, 'childNodes');
- processUnderlineAndColor(curSelNode);
- }
- selection.moveToBookmark(bookmark);
- moveStart(selection.getRng(TRUE));
- ed.nodeChanged();
- } else {
- performCaretAction('apply', name, vars);
- }
- }
- Hooks.postProcess(name, ed);
- }
- }
- /**
- * Removes the specified format from the current selection or specified node.
- *
- * @method remove
- * @param {String} name Name of format to remove.
- * @param {Object} vars Optional list of variables to replace within format before removing it.
- * @param {Node/Range} node Optional node or DOM range to remove the format from defaults to current selection.
- */
- function remove(name, vars, node, similar) {
- var formatList = get(name), format = formatList[0], bookmark, rng, contentEditable = true;
- // Merges the styles for each node
- function process(node) {
- var children, i, l, lastContentEditable, hasContentEditableState;
- // Node has a contentEditable value
- if (node.nodeType === 1 && getContentEditable(node)) {
- lastContentEditable = contentEditable;
- contentEditable = getContentEditable(node) === "true";
- hasContentEditableState = true; // We don't want to wrap the container only it's children
- }
- // Grab the children first since the nodelist might be changed
- children = grep(node.childNodes);
- // Process current node
- if (contentEditable && !hasContentEditableState) {
- for (i = 0, l = formatList.length; i < l; i++) {
- if (removeFormat(formatList[i], vars, node, node)) {
- break;
- }
- }
- }
- // Process the children
- if (format.deep) {
- if (children.length) {
- for (i = 0, l = children.length; i < l; i++) {
- process(children[i]);
- }
- if (hasContentEditableState) {
- contentEditable = lastContentEditable; // Restore last contentEditable state from stack
- }
- }
- }
- }
- function findFormatRoot(container) {
- var formatRoot;
- // Find format root
- each(getParents(container.parentNode).reverse(), function(parent) {
- var format;
- // Find format root element
- if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
- // Is the node matching the format we are looking for
- format = matchNode(parent, name, vars, similar);
- if (format && format.split !== false) {
- formatRoot = parent;
- }
- }
- });
- return formatRoot;
- }
- function wrapAndSplit(formatRoot, container, target, split) {
- var parent, clone, lastClone, firstClone, i, formatRootParent;
- // Format root found then clone formats and split it
- if (formatRoot) {
- formatRootParent = formatRoot.parentNode;
- for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
- clone = dom.clone(parent, FALSE);
- for (i = 0; i < formatList.length; i++) {
- if (removeFormat(formatList[i], vars, clone, clone)) {
- clone = 0;
- break;
- }
- }
- // Build wrapper node
- if (clone) {
- if (lastClone) {
- clone.appendChild(lastClone);
- }
- if (!firstClone) {
- firstClone = clone;
- }
- lastClone = clone;
- }
- }
- // Never split block elements if the format is mixed
- if (split && (!format.mixed || !isBlock(formatRoot))) {
- container = dom.split(formatRoot, container);
- }
- // Wrap container in cloned formats
- if (lastClone) {
- target.parentNode.insertBefore(lastClone, target);
- firstClone.appendChild(target);
- }
- }
- return container;
- }
- function splitToFormatRoot(container) {
- return wrapAndSplit(findFormatRoot(container), container, container, true);
- }
- function unwrap(start) {
- var node = dom.get(start ? '_start' : '_end'),
- out = node[start ? 'firstChild' : 'lastChild'];
- // If the end is placed within the start the result will be removed
- // So this checks if the out node is a bookmark node if it is it
- // checks for another more suitable node
- if (isBookmarkNode(out)) {
- out = out[start ? 'firstChild' : 'lastChild'];
- }
- // Since dom.remove removes empty text nodes then we need to try to find a better node
- if (out.nodeType == 3 && out.data.length === 0) {
- out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
- }
- dom.remove(node, true);
- return out;
- }
- function removeRngStyle(rng) {
- var startContainer, endContainer;
- var commonAncestorContainer = rng.commonAncestorContainer;
- rng = expandRng(rng, formatList, TRUE);
- if (format.split) {
- startContainer = getContainer(rng, TRUE);
- endContainer = getContainer(rng);
- if (startContainer != endContainer) {
- // WebKit will render the table incorrectly if we wrap a TH or TD in a SPAN
- // so let's see if we can use the first child instead
- // This will happen if you triple click a table cell and use remove formatting
- if (/^(TR|TH|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
- if (startContainer.nodeName == "TR") {
- startContainer = startContainer.firstChild.firstChild || startContainer;
- } else {
- startContainer = startContainer.firstChild || startContainer;
- }
- }
- // Try to adjust endContainer as well if cells on the same row were selected - bug #6410
- if (commonAncestorContainer &&
- /^T(HEAD|BODY|FOOT|R)$/.test(commonAncestorContainer.nodeName) &&
- isTableCell(endContainer) && endContainer.firstChild) {
- endContainer = endContainer.firstChild || endContainer;
- }
- if (dom.isChildOf(startContainer, endContainer) && !isBlock(endContainer) &&
- !isTableCell(startContainer) && !isTableCell(endContainer)) {
- startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
- splitToFormatRoot(startContainer);
- startContainer = unwrap(TRUE);
- return;
- }
- // Wrap start/end nodes in span element since these might be cloned/moved
- startContainer = wrap(startContainer, 'span', {id: '_start', 'data-mce-type': 'bookmark'});
- endContainer = wrap(endContainer, 'span', {id: '_end', 'data-mce-type': 'bookmark'});
- // Split start/end
- splitToFormatRoot(startContainer);
- splitToFormatRoot(endContainer);
- // Unwrap start/end to get real elements again
- startContainer = unwrap(TRUE);
- endContainer = unwrap();
- } else {
- startContainer = endContainer = splitToFormatRoot(startContainer);
- }
- // Update range positions since they might have changed after the split operations
- rng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
- rng.startOffset = nodeIndex(startContainer);
- rng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
- rng.endOffset = nodeIndex(endContainer) + 1;
- }
- // Remove items between start/end
- rangeUtils.walk(rng, function(nodes) {
- each(nodes, function(node) {
- process(node);
- // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
- if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' &&
- node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
- removeFormat({
- 'deep': false,
- 'exact': true,
- 'inline': 'span',
- 'styles': {
- 'textDecoration': 'underline'
- }
- }, null, node);
- }
- });
- });
- }
- // Handle node
- if (node) {
- if (node.nodeType) {
- rng = dom.createRng();
- rng.setStartBefore(node);
- rng.setEndAfter(node);
- removeRngStyle(rng);
- } else {
- removeRngStyle(node);
- }
- return;
- }
- if (getContentEditable(selection.getNode()) === "false") {
- node = selection.getNode();
- for (var i = 0, l = formatList.length; i < l; i++) {
- if (formatList[i].ceFalseOverride) {
- if (removeFormat(formatList[i], vars, node, node)) {
- break;
- }
- }
- }
- return;
- }
- if (!selection.isCollapsed() || !format.inline || dom.select('td[data-mce-selected],th[data-mce-selected]').length) {
- bookmark = selection.getBookmark();
- removeRngStyle(selection.getRng(TRUE));
- selection.moveToBookmark(bookmark);
- // Check if start element still has formatting then we are at: "<b>text|</b>text"
- // and need to move the start into the next text node
- if (format.inline && match(name, vars, selection.getStart())) {
- moveStart(selection.getRng(true));
- }
- ed.nodeChanged();
- } else {
- performCaretAction('remove', name, vars, similar);
- }
- }
- /**
- * Toggles the specified format on/off.
- *
- * @method toggle
- * @param {String} name Name of format to apply/remove.
- * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
- * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
- */
- function toggle(name, vars, node) {
- var fmt = get(name);
- if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
- remove(name, vars, node);
- } else {
- apply(name, vars, node);
- }
- }
- /**
- * Return true/false if the specified node has the specified format.
- *
- * @method matchNode
- * @param {Node} node Node to check the format on.
- * @param {String} name Format name to check.
- * @param {Object} vars Optional list of variables to replace before checking it.
- * @param {Boolean} similar Match format that has similar properties.
- * @return {Object} Returns the format object it matches or undefined if it doesn't match.
- */
- function matchNode(node, name, vars, similar) {
- var formatList = get(name), format, i, classes;
- function matchItems(node, format, item_name) {
- var key, value, items = format[item_name], i;
- // Custom match
- if (format.onmatch) {
- return format.onmatch(node, format, item_name);
- }
- // Check all items
- if (items) {
- // Non indexed object
- if (items.length === undef) {
- for (key in items) {
- if (items.hasOwnProperty(key)) {
- if (item_name === 'attributes') {
- value = dom.getAttrib(node, key);
- } else {
- value = getStyle(node, key);
- }
- if (similar && !value && !format.exact) {
- return;
- }
- if ((!similar || format.exact) && !isEq(value, normalizeStyleValue(replaceVars(items[key], vars), key))) {
- return;
- }
- }
- }
- } else {
- // Only one match needed for indexed arrays
- for (i = 0; i < items.length; i++) {
- if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i])) {
- return format;
- }
- }
- }
- }
- return format;
- }
- if (formatList && node) {
- // Check each format in list
- for (i = 0; i < formatList.length; i++) {
- format = formatList[i];
- // Name name, attributes, styles and classes
- if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
- // Match classes
- if ((classes = format.classes)) {
- for (i = 0; i < classes.length; i++) {
- if (!dom.hasClass(node, classes[i])) {
- return;
- }
- }
- }
- return format;
- }
- }
- }
- }
- /**
- * Matches the current selection or specified node against the specified format name.
- *
- * @method match
- * @param {String} name Name of format to match.
- * @param {Object} vars Optional list of variables to replace before checking it.
- * @param {Node} node Optional node to check.
- * @return {boolean} true/false if the specified selection/node matches the format.
- */
- function match(name, vars, node) {
- var startNode;
- function matchParents(node) {
- var root = dom.getRoot();
- if (node === root) {
- return false;
- }
- // Find first node with similar format settings
- node = dom.getParent(node, function(node) {
- if (matchesUnInheritedFormatSelector(node, name)) {
- return true;
- }
- return node.parentNode === root || !!matchNode(node, name, vars, true);
- });
- // Do an exact check on the similar format element
- return matchNode(node, name, vars);
- }
- // Check specified node
- if (node) {
- return matchParents(node);
- }
- // Check selected node
- node = selection.getNode();
- if (matchParents(node)) {
- return TRUE;
- }
- // Check start node if it's different
- startNode = selection.getStart();
- if (startNode != node) {
- if (matchParents(startNode)) {
- return TRUE;
- }
- }
- return FALSE;
- }
- /**
- * Matches the current selection against the array of formats and returns a new array with matching formats.
- *
- * @method matchAll
- * @param {Array} names Name of format to match.
- * @param {Object} vars Optional list of variables to replace before checking it.
- * @return {Array} Array with matched formats.
- */
- function matchAll(names, vars) {
- var startElement, matchedFormatNames = [], checkedMap = {};
- // Check start of selection for formats
- startElement = selection.getStart();
- dom.getParent(startElement, function(node) {
- var i, name;
- for (i = 0; i < names.length; i++) {
- name = names[i];
- if (!checkedMap[name] && matchNode(node, name, vars)) {
- checkedMap[name] = true;
- matchedFormatNames.push(name);
- }
- }
- }, dom.getRoot());
- return matchedFormatNames;
- }
- /**
- * Returns true/false if the specified format can be applied to the current selection or not. It
- * will currently only check the state for selector formats, it returns true on all other format types.
- *
- * @method canApply
- * @param {String} name Name of format to check.
- * @return {boolean} true/false if the specified format can be applied to the current selection/node.
- */
- function canApply(name) {
- var formatList = get(name), startNode, parents, i, x, selector;
- if (formatList) {
- startNode = selection.getStart();
- parents = getParents(startNode);
- for (x = formatList.length - 1; x >= 0; x--) {
- selector = formatList[x].selector;
- // Format is not selector based then always return TRUE
- // Is it has a defaultBlock then it's likely it can be applied for example align on a non block element line
- if (!selector || formatList[x].defaultBlock) {
- return TRUE;
- }
- for (i = parents.length - 1; i >= 0; i--) {
- if (dom.is(parents[i], selector)) {
- return TRUE;
- }
- }
- }
- }
- return FALSE;
- }
- /**
- * Executes the specified callback when the current selection matches the formats or not.
- *
- * @method formatChanged
- * @param {String} formats Comma separated list of formats to check for.
- * @param {function} callback Callback with state and args when the format is changed/toggled on/off.
- * @param {Boolean} similar True/false state if the match should handle similar or exact formats.
- */
- function formatChanged(formats, callback, similar) {
- var currentFormats;
- // Setup format node change logic
- if (!formatChangeData) {
- formatChangeData = {};
- currentFormats = {};
- ed.on('NodeChange', function(e) {
- var parents = getParents(e.element), matchedFormats = {};
- // Ignore bogus nodes like the <a> tag created by moveStart()
- parents = Tools.grep(parents, function(node) {
- return node.nodeType == 1 && !node.getAttribute('data-mce-bogus');
- });
- // Check for new formats
- each(formatChangeData, function(callbacks, format) {
- each(parents, function(node) {
- if (matchNode(node, format, {}, callbacks.similar)) {
- if (!currentFormats[format]) {
- // Execute callbacks
- each(callbacks, function(callback) {
- callback(true, {node: node, format: format, parents: parents});
- });
- currentFormats[format] = callbacks;
- }
- matchedFormats[format] = callbacks;
- return false;
- }
- if (matchesUnInheritedFormatSelector(node, format)) {
- return false;
- }
- });
- });
- // Check if current formats still match
- each(currentFormats, function(callbacks, format) {
- if (!matchedFormats[format]) {
- delete currentFormats[format];
- each(callbacks, function(callback) {
- callback(false, {node: e.element, format: format, parents: parents});
- });
- }
- });
- });
- }
- // Add format listeners
- each(formats.split(','), function(format) {
- if (!formatChangeData[format]) {
- formatChangeData[format] = [];
- formatChangeData[format].similar = similar;
- }
- formatChangeData[format].push(callback);
- });
- return this;
- }
- /**
- * Returns a preview css text for the specified format.
- *
- * @method getCssText
- * @param {String/Object} format Format to generate preview css text for.
- * @return {String} Css text for the specified format.
- * @example
- * var cssText1 = editor.formatter.getCssText('bold');
- * var cssText2 = editor.formatter.getCssText({inline: 'b'});
- */
- function getCssText(format) {
- return Preview.getCssText(ed, format);
- }
- // Expose to public
- extend(this, {
- get: get,
- register: register,
- unregister: unregister,
- apply: apply,
- remove: remove,
- toggle: toggle,
- match: match,
- matchAll: matchAll,
- matchNode: matchNode,
- canApply: canApply,
- formatChanged: formatChanged,
- getCssText: getCssText
- });
- // Initialize
- defaultFormats();
- addKeyboardShortcuts();
- ed.on('BeforeGetContent', function(e) {
- if (markCaretContainersBogus && e.format != 'raw') {
- markCaretContainersBogus();
- }
- });
- ed.on('mouseup keydown', function(e) {
- if (disableCaretContainer) {
- disableCaretContainer(e);
- }
- });
- // Private functions
- /**
- * Checks if the specified nodes name matches the format inline/block or selector.
- *
- * @private
- * @param {Node} node Node to match against the specified format.
- * @param {Object} format Format object o match with.
- * @return {boolean} true/false if the format matches.
- */
- function matchName(node, format) {
- // Check for inline match
- if (isEq(node, format.inline)) {
- return TRUE;
- }
- // Check for block match
- if (isEq(node, format.block)) {
- return TRUE;
- }
- // Check for selector match
- if (format.selector) {
- return node.nodeType == 1 && dom.is(node, format.selector);
- }
- }
- /**
- * Compares two string/nodes regardless of their case.
- *
- * @private
- * @param {String/Node} str1 Node or string to compare.
- * @param {String/Node} str2 Node or string to compare.
- * @return {boolean} True/false if they match.
- */
- function isEq(str1, str2) {
- str1 = str1 || '';
- str2 = str2 || '';
- str1 = '' + (str1.nodeName || str1);
- str2 = '' + (str2.nodeName || str2);
- return str1.toLowerCase() == str2.toLowerCase();
- }
- /**
- * Returns the style by name on the specified node. This method modifies the style
- * contents to make it more easy to match. This will resolve a few browser issues.
- *
- * @private
- * @param {Node} node to get style from.
- * @param {String} name Style name to get.
- * @return {String} Style item value.
- */
- function getStyle(node, name) {
- return normalizeStyleValue(dom.getStyle(node, name), name);
- }
- /**
- * Normalize style value by name. This method modifies the style contents
- * to make it more easy to match. This will resolve a few browser issues.
- *
- * @private
- * @param {String} value Value to get style from.
- * @param {String} name Style name to get.
- * @return {String} Style item value.
- */
- function normalizeStyleValue(value, name) {
- // Force the format to hex
- if (name == 'color' || name == 'backgroundColor') {
- value = dom.toHex(value);
- }
- // Opera will return bold as 700
- if (name == 'fontWeight' && value == 700) {
- value = 'bold';
- }
- // Normalize fontFamily so "'Font name', Font" becomes: "Font name,Font"
- if (name == 'fontFamily') {
- value = value.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
- }
- return '' + value;
- }
- /**
- * Replaces variables in the value. The variable format is %var.
- *
- * @private
- * @param {String} value Value to replace variables in.
- * @param {Object} vars Name/value array with variables to replace.
- * @return {String} New value with replaced variables.
- */
- function replaceVars(value, vars) {
- if (typeof value != "string") {
- value = value(vars);
- } else if (vars) {
- value = value.replace(/%(\w+)/g, function(str, name) {
- return vars[name] || str;
- });
- }
- return value;
- }
- function isWhiteSpaceNode(node) {
- return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
- }
- function wrap(node, name, attrs) {
- var wrapper = dom.create(name, attrs);
- node.parentNode.insertBefore(wrapper, node);
- wrapper.appendChild(node);
- return wrapper;
- }
- /**
- * Expands the specified range like object to depending on format.
- *
- * For example on block formats it will move the start/end position
- * to the beginning of the current block.
- *
- * @private
- * @param {Object} rng Range like object.
- * @param {Array} format Array with formats to expand by.
- * @param {Boolean} remove
- * @return {Object} Expanded range like object.
- */
- function expandRng(rng, format, remove) {
- var lastIdx, leaf, endPoint,
- startContainer = rng.startContainer,
- startOffset = rng.startOffset,
- endContainer = rng.endContainer,
- endOffset = rng.endOffset;
- // This function walks up the tree if there is no siblings before/after the node
- function findParentContainer(start) {
- var container, parent, sibling, siblingName, root;
- container = parent = start ? startContainer : endContainer;
- siblingName = start ? 'previousSibling' : 'nextSibling';
- root = dom.getRoot();
- function isBogusBr(node) {
- return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
- }
- // If it's a text node and the offset is inside the text
- if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
- if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
- return container;
- }
- }
- /*eslint no-constant-condition:0 */
- while (true) {
- // Stop expanding on block elements
- if (!format[0].block_expand && isBlock(parent)) {
- return parent;
- }
- // Walk left/right
- for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
- if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
- return parent;
- }
- }
- // Check if we can move up are we at root level or body level
- if (parent == root || parent.parentNode == root) {
- container = parent;
- break;
- }
- parent = parent.parentNode;
- }
- return container;
- }
- // This function walks down the tree to find the leaf at the selection.
- // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
- function findLeaf(node, offset) {
- if (offset === undef) {
- offset = node.nodeType === 3 ? node.length : node.childNodes.length;
- }
- while (node && node.hasChildNodes()) {
- node = node.childNodes[offset];
- if (node) {
- offset = node.nodeType === 3 ? node.length : node.childNodes.length;
- }
- }
- return {node: node, offset: offset};
- }
- // If index based start position then resolve it
- if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
- lastIdx = startContainer.childNodes.length - 1;
- startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
- if (startContainer.nodeType == 3) {
- startOffset = 0;
- }
- }
- // If index based end position then resolve it
- if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
- lastIdx = endContainer.childNodes.length - 1;
- endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
- if (endContainer.nodeType == 3) {
- endOffset = endContainer.nodeValue.length;
- }
- }
- // Expands the node to the closes contentEditable false element if it exists
- function findParentContentEditable(node) {
- var parent = node;
- while (parent) {
- if (parent.nodeType === 1 && getContentEditable(parent)) {
- return getContentEditable(parent) === "false" ? parent : node;
- }
- parent = parent.parentNode;
- }
- return node;
- }
- function findWordEndPoint(container, offset, start) {
- var walker, node, pos, lastTextNode;
- function findSpace(node, offset) {
- var pos, pos2, str = node.nodeValue;
- if (typeof offset == "undefined") {
- offset = start ? str.length : 0;
- }
- if (start) {
- pos = str.lastIndexOf(' ', offset);
- pos2 = str.lastIndexOf('\u00a0', offset);
- pos = pos > pos2 ? pos : pos2;
- // Include the space on remove to avoid tag soup
- if (pos !== -1 && !remove) {
- pos++;
- }
- } else {
- pos = str.indexOf(' ', offset);
- pos2 = str.indexOf('\u00a0', offset);
- pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
- }
- return pos;
- }
- if (container.nodeType === 3) {
- pos = findSpace(container, offset);
- if (pos !== -1) {
- return {container: container, offset: pos};
- }
- lastTextNode = container;
- }
- // Walk the nodes inside the block
- walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
- while ((node = walker[start ? 'prev' : 'next']())) {
- if (node.nodeType === 3) {
- lastTextNode = node;
- pos = findSpace(node);
- if (pos !== -1) {
- return {container: node, offset: pos};
- }
- } else if (isBlock(node)) {
- break;
- }
- }
- if (lastTextNode) {
- if (start) {
- offset = 0;
- } else {
- offset = lastTextNode.length;
- }
- return {container: lastTextNode, offset: offset};
- }
- }
- function findSelectorEndPoint(container, sibling_name) {
- var parents, i, y, curFormat;
- if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name]) {
- container = container[sibling_name];
- }
- parents = getParents(container);
- for (i = 0; i < parents.length; i++) {
- for (y = 0; y < format.length; y++) {
- curFormat = format[y];
- // If collapsed state is set then skip formats that doesn't match that
- if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) {
- continue;
- }
- if (dom.is(parents[i], curFormat.selector)) {
- return parents[i];
- }
- }
- }
- return container;
- }
- function findBlockEndPoint(container, sibling_name) {
- var node, root = dom.getRoot();
- // Expand to block of similar type
- if (!format[0].wrapper) {
- node = dom.getParent(container, format[0].block, root);
- }
- // Expand to first wrappable block element or any block element
- if (!node) {
- node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, function(node) {
- // Fixes #6183 where it would expand to editable parent element in inline mode
- return node != root && isTextBlock(node);
- });
- }
- // Exclude inner lists from wrapping
- if (node && format[0].wrapper) {
- node = getParents(node, 'ul,ol').reverse()[0] || node;
- }
- // Didn't find a block element look for first/last wrappable element
- if (!node) {
- node = container;
- while (node[sibling_name] && !isBlock(node[sibling_name])) {
- node = node[sibling_name];
- // Break on BR but include it will be removed later on
- // we can't remove it now since we need to check if it can be wrapped
- if (isEq(node, 'br')) {
- break;
- }
- }
- }
- return node || container;
- }
- // Expand to closest contentEditable element
- startContainer = findParentContentEditable(startContainer);
- endContainer = findParentContentEditable(endContainer);
- // Exclude bookmark nodes if possible
- if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
- startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
- startContainer = startContainer.nextSibling || startContainer;
- if (startContainer.nodeType == 3) {
- startOffset = 0;
- }
- }
- if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
- endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
- endContainer = endContainer.previousSibling || endContainer;
- if (endContainer.nodeType == 3) {
- endOffset = endContainer.length;
- }
- }
- if (format[0].inline) {
- if (rng.collapsed) {
- // Expand left to closest word boundary
- endPoint = findWordEndPoint(startContainer, startOffset, true);
- if (endPoint) {
- startContainer = endPoint.container;
- startOffset = endPoint.offset;
- }
- // Expand right to closest word boundary
- endPoint = findWordEndPoint(endContainer, endOffset);
- if (endPoint) {
- endContainer = endPoint.container;
- endOffset = endPoint.offset;
- }
- }
- // Avoid applying formatting to a trailing space.
- leaf = findLeaf(endContainer, endOffset);
- if (leaf.node) {
- while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) {
- leaf = findLeaf(leaf.node.previousSibling);
- }
- if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
- leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
- if (leaf.offset > 1) {
- endContainer = leaf.node;
- endContainer.splitText(leaf.offset - 1);
- }
- }
- }
- }
- // Move start/end point up the tree if the leaves are sharp and if we are in different containers
- // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
- // This will reduce the number of wrapper elements that needs to be created
- // Move start point up the tree
- if (format[0].inline || format[0].block_expand) {
- if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
- startContainer = findParentContainer(true);
- }
- if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
- endContainer = findParentContainer();
- }
- }
- // Expand start/end container to matching selector
- if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
- // Find new startContainer/endContainer if there is better one
- startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
- endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
- }
- // Expand start/end container to matching block element or text node
- if (format[0].block || format[0].selector) {
- // Find new startContainer/endContainer if there is better one
- startContainer = findBlockEndPoint(startContainer, 'previousSibling');
- endContainer = findBlockEndPoint(endContainer, 'nextSibling');
- // Non block element then try to expand up the leaf
- if (format[0].block) {
- if (!isBlock(startContainer)) {
- startContainer = findParentContainer(true);
- }
- if (!isBlock(endContainer)) {
- endContainer = findParentContainer();
- }
- }
- }
- // Setup index for startContainer
- if (startContainer.nodeType == 1) {
- startOffset = nodeIndex(startContainer);
- startContainer = startContainer.parentNode;
- }
- // Setup index for endContainer
- if (endContainer.nodeType == 1) {
- endOffset = nodeIndex(endContainer) + 1;
- endContainer = endContainer.parentNode;
- }
- // Return new range like object
- return {
- startContainer: startContainer,
- startOffset: startOffset,
- endContainer: endContainer,
- endOffset: endOffset
- };
- }
- function isColorFormatAndAnchor(node, format) {
- return format.links && node.tagName == 'A';
- }
- /**
- * Removes the specified format for the specified node. It will also remove the node if it doesn't have
- * any attributes if the format specifies it to do so.
- *
- * @private
- * @param {Object} format Format object with items to remove from node.
- * @param {Object} vars Name/value object with variables to apply to format.
- * @param {Node} node Node to remove the format styles on.
- * @param {Node} compare_node Optional compare node, if specified the styles will be compared to that node.
- * @return {Boolean} True/false if the node was removed or not.
- */
- function removeFormat(format, vars, node, compare_node) {
- var i, attrs, stylesModified;
- // Check if node matches format
- if (!matchName(node, format) && !isColorFormatAndAnchor(node, format)) {
- return FALSE;
- }
- // Should we compare with format attribs and styles
- if (format.remove != 'all') {
- // Remove styles
- each(format.styles, function(value, name) {
- value = normalizeStyleValue(replaceVars(value, vars), name);
- // Indexed array
- if (typeof name === 'number') {
- name = value;
- compare_node = 0;
- }
- if (format.remove_similar || (!compare_node || isEq(getStyle(compare_node, name), value))) {
- dom.setStyle(node, name, '');
- }
- stylesModified = 1;
- });
- // Remove style attribute if it's empty
- if (stylesModified && dom.getAttrib(node, 'style') === '') {
- node.removeAttribute('style');
- node.removeAttribute('data-mce-style');
- }
- // Remove attributes
- each(format.attributes, function(value, name) {
- var valueOut;
- value = replaceVars(value, vars);
- // Indexed array
- if (typeof name === 'number') {
- name = value;
- compare_node = 0;
- }
- if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
- // Keep internal classes
- if (name == 'class') {
- value = dom.getAttrib(node, name);
- if (value) {
- // Build new class value where everything is removed except the internal prefixed classes
- valueOut = '';
- each(value.split(/\s+/), function(cls) {
- if (/mce\-\w+/.test(cls)) {
- valueOut += (valueOut ? ' ' : '') + cls;
- }
- });
- // We got some internal classes left
- if (valueOut) {
- dom.setAttrib(node, name, valueOut);
- return;
- }
- }
- }
- // IE6 has a bug where the attribute doesn't get removed correctly
- if (name == "class") {
- node.removeAttribute('className');
- }
- // Remove mce prefixed attributes
- if (MCE_ATTR_RE.test(name)) {
- node.removeAttribute('data-mce-' + name);
- }
- node.removeAttribute(name);
- }
- });
- // Remove classes
- each(format.classes, function(value) {
- value = replaceVars(value, vars);
- if (!compare_node || dom.hasClass(compare_node, value)) {
- dom.removeClass(node, value);
- }
- });
- // Check for non internal attributes
- attrs = dom.getAttribs(node);
- for (i = 0; i < attrs.length; i++) {
- if (attrs[i].nodeName.indexOf('_') !== 0) {
- return FALSE;
- }
- }
- }
- // Remove the inline child if it's empty for example <b> or <span>
- if (format.remove != 'none') {
- removeNode(node, format);
- return TRUE;
- }
- }
- /**
- * Removes the node and wrap it's children in paragraphs before doing so or
- * appends BR elements to the beginning/end of the block element if forcedRootBlocks is disabled.
- *
- * If the div in the node below gets removed:
- * text<div>text</div>text
- *
- * Output becomes:
- * text<div><br />text<br /></div>text
- *
- * So when the div is removed the result is:
- * text<br />text<br />text
- *
- * @private
- * @param {Node} node Node to remove + apply BR/P elements to.
- * @param {Object} format Format rule.
- * @return {Node} Input node.
- */
- function removeNode(node, format) {
- var parentNode = node.parentNode, rootBlockElm;
- function find(node, next, inc) {
- node = getNonWhiteSpaceSibling(node, next, inc);
- return !node || (node.nodeName == 'BR' || isBlock(node));
- }
- if (format.block) {
- if (!forcedRootBlock) {
- // Append BR elements if needed before we remove the block
- if (isBlock(node) && !isBlock(parentNode)) {
- if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1)) {
- node.insertBefore(dom.create('br'), node.firstChild);
- }
- if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1)) {
- node.appendChild(dom.create('br'));
- }
- }
- } else {
- // Wrap the block in a forcedRootBlock if we are at the root of document
- if (parentNode == dom.getRoot()) {
- if (!format.list_block || !isEq(node, format.list_block)) {
- each(grep(node.childNodes), function(node) {
- if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
- if (!rootBlockElm) {
- rootBlockElm = wrap(node, forcedRootBlock);
- dom.setAttribs(rootBlockElm, ed.settings.forced_root_block_attrs);
- } else {
- rootBlockElm.appendChild(node);
- }
- } else {
- rootBlockElm = 0;
- }
- });
- }
- }
- }
- }
- // Never remove nodes that isn't the specified inline element if a selector is specified too
- if (format.selector && format.inline && !isEq(format.inline, node)) {
- return;
- }
- dom.remove(node, 1);
- }
- /**
- * Returns the next/previous non whitespace node.
- *
- * @private
- * @param {Node} node Node to start at.
- * @param {boolean} next (Optional) Include next or previous node defaults to previous.
- * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
- * @return {Node} Next or previous node or undefined if it wasn't found.
- */
- function getNonWhiteSpaceSibling(node, next, inc) {
- if (node) {
- next = next ? 'nextSibling' : 'previousSibling';
- for (node = inc ? node : node[next]; node; node = node[next]) {
- if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
- return node;
- }
- }
- }
- }
- /**
- * Merges the next/previous sibling element if they match.
- *
- * @private
- * @param {Node} prev Previous node to compare/merge.
- * @param {Node} next Next node to compare/merge.
- * @return {Node} Next node if we didn't merge and prev node if we did.
- */
- function mergeSiblings(prev, next) {
- var sibling, tmpSibling, elementUtils = new ElementUtils(dom);
- function findElementSibling(node, sibling_name) {
- for (sibling = node; sibling; sibling = sibling[sibling_name]) {
- if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) {
- return node;
- }
- if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) {
- return sibling;
- }
- }
- return node;
- }
- // Check if next/prev exists and that they are elements
- if (prev && next) {
- // If previous sibling is empty then jump over it
- prev = findElementSibling(prev, 'previousSibling');
- next = findElementSibling(next, 'nextSibling');
- // Compare next and previous nodes
- if (elementUtils.compare(prev, next)) {
- // Append nodes between
- for (sibling = prev.nextSibling; sibling && sibling != next;) {
- tmpSibling = sibling;
- sibling = sibling.nextSibling;
- prev.appendChild(tmpSibling);
- }
- // Remove next node
- dom.remove(next);
- // Move children into prev node
- each(grep(next.childNodes), function(node) {
- prev.appendChild(node);
- });
- return prev;
- }
- }
- return next;
- }
- function getContainer(rng, start) {
- var container, offset, lastIdx;
- container = rng[start ? 'startContainer' : 'endContainer'];
- offset = rng[start ? 'startOffset' : 'endOffset'];
- if (container.nodeType == 1) {
- lastIdx = container.childNodes.length - 1;
- if (!start && offset) {
- offset--;
- }
- container = container.childNodes[offset > lastIdx ? lastIdx : offset];
- }
- // If start text node is excluded then walk to the next node
- if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
- container = new TreeWalker(container, ed.getBody()).next() || container;
- }
- // If end text node is excluded then walk to the previous node
- if (container.nodeType === 3 && !start && offset === 0) {
- container = new TreeWalker(container, ed.getBody()).prev() || container;
- }
- return container;
- }
- function performCaretAction(type, name, vars, similar) {
- var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
- // Creates a caret container bogus element
- function createCaretContainer(fill) {
- var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
- if (fill) {
- caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
- }
- return caretContainer;
- }
- function isCaretContainerEmpty(node, nodes) {
- while (node) {
- if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
- return false;
- }
- // Collect nodes
- if (nodes && node.nodeType === 1) {
- nodes.push(node);
- }
- node = node.firstChild;
- }
- return true;
- }
- // Returns any parent caret container element
- function getParentCaretContainer(node) {
- while (node) {
- if (node.id === caretContainerId) {
- return node;
- }
- node = node.parentNode;
- }
- }
- // Finds the first text node in the specified node
- function findFirstTextNode(node) {
- var walker;
- if (node) {
- walker = new TreeWalker(node, node);
- for (node = walker.current(); node; node = walker.next()) {
- if (node.nodeType === 3) {
- return node;
- }
- }
- }
- }
- // Removes the caret container for the specified node or all on the current document
- function removeCaretContainer(node, move_caret) {
- var child, rng;
- if (!node) {
- node = getParentCaretContainer(selection.getStart());
- if (!node) {
- while ((node = dom.get(caretContainerId))) {
- removeCaretContainer(node, false);
- }
- }
- } else {
- rng = selection.getRng(true);
- if (isCaretContainerEmpty(node)) {
- if (move_caret !== false) {
- rng.setStartBefore(node);
- rng.setEndBefore(node);
- }
- dom.remove(node);
- } else {
- child = findFirstTextNode(node);
- if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
- child.deleteData(0, 1);
- // Fix for bug #6976
- if (rng.startContainer == child && rng.startOffset > 0) {
- rng.setStart(child, rng.startOffset - 1);
- }
- if (rng.endContainer == child && rng.endOffset > 0) {
- rng.setEnd(child, rng.endOffset - 1);
- }
- }
- dom.remove(node, 1);
- }
- selection.setRng(rng);
- }
- }
- // Applies formatting to the caret position
- function applyCaretFormat() {
- var rng, caretContainer, textNode, offset, bookmark, container, text;
- rng = selection.getRng(true);
- offset = rng.startOffset;
- container = rng.startContainer;
- text = container.nodeValue;
- caretContainer = getParentCaretContainer(selection.getStart());
- if (caretContainer) {
- textNode = findFirstTextNode(caretContainer);
- }
- // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
- if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
- // Get bookmark of caret position
- bookmark = selection.getBookmark();
- // Collapse bookmark range (WebKit)
- rng.collapse(true);
- // Expand the range to the closest word and split it at those points
- rng = expandRng(rng, get(name));
- rng = rangeUtils.split(rng);
- // Apply the format to the range
- apply(name, vars, rng);
- // Move selection back to caret position
- selection.moveToBookmark(bookmark);
- } else {
- if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
- caretContainer = createCaretContainer(true);
- textNode = caretContainer.firstChild;
- rng.insertNode(caretContainer);
- offset = 1;
- apply(name, vars, caretContainer);
- } else {
- apply(name, vars, caretContainer);
- }
- // Move selection to text node
- selection.setCursorLocation(textNode, offset);
- }
- }
- function removeCaretFormat() {
- var rng = selection.getRng(true), container, offset, bookmark,
- hasContentAfter, node, formatNode, parents = [], i, caretContainer;
- container = rng.startContainer;
- offset = rng.startOffset;
- node = container;
- if (container.nodeType == 3) {
- if (offset != container.nodeValue.length) {
- hasContentAfter = true;
- }
- node = node.parentNode;
- }
- while (node) {
- if (matchNode(node, name, vars, similar)) {
- formatNode = node;
- break;
- }
- if (node.nextSibling) {
- hasContentAfter = true;
- }
- parents.push(node);
- node = node.parentNode;
- }
- // Node doesn't have the specified format
- if (!formatNode) {
- return;
- }
- // Is there contents after the caret then remove the format on the element
- if (hasContentAfter) {
- // Get bookmark of caret position
- bookmark = selection.getBookmark();
- // Collapse bookmark range (WebKit)
- rng.collapse(true);
- // Expand the range to the closest word and split it at those points
- rng = expandRng(rng, get(name), true);
- rng = rangeUtils.split(rng);
- // Remove the format from the range
- remove(name, vars, rng);
- // Move selection back to caret position
- selection.moveToBookmark(bookmark);
- } else {
- caretContainer = createCaretContainer();
- node = caretContainer;
- for (i = parents.length - 1; i >= 0; i--) {
- node.appendChild(dom.clone(parents[i], false));
- node = node.firstChild;
- }
- // Insert invisible character into inner most format element
- node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
- node = node.firstChild;
- var block = dom.getParent(formatNode, isTextBlock);
- if (block && dom.isEmpty(block)) {
- // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
- formatNode.parentNode.replaceChild(caretContainer, formatNode);
- } else {
- // Insert caret container after the formatted node
- dom.insertAfter(caretContainer, formatNode);
- }
- // Move selection to text node
- selection.setCursorLocation(node, 1);
- // If the formatNode is empty, we can remove it safely.
- if (dom.isEmpty(formatNode)) {
- dom.remove(formatNode);
- }
- }
- }
- // Checks if the parent caret container node isn't empty if that is the case it
- // will remove the bogus state on all children that isn't empty
- function unmarkBogusCaretParents() {
- var caretContainer;
- caretContainer = getParentCaretContainer(selection.getStart());
- if (caretContainer && !dom.isEmpty(caretContainer)) {
- walk(caretContainer, function(node) {
- if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
- dom.setAttrib(node, 'data-mce-bogus', null);
- }
- }, 'childNodes');
- }
- }
- // Only bind the caret events once
- if (!ed._hasCaretEvents) {
- // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
- markCaretContainersBogus = function() {
- var nodes = [], i;
- if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
- // Mark children
- i = nodes.length;
- while (i--) {
- dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
- }
- }
- };
- disableCaretContainer = function(e) {
- var keyCode = e.keyCode;
- removeCaretContainer();
- // Remove caret container if it's empty
- if (keyCode == 8 && selection.isCollapsed() && selection.getStart().innerHTML == INVISIBLE_CHAR) {
- removeCaretContainer(getParentCaretContainer(selection.getStart()));
- }
- // Remove caret container on keydown and it's left/right arrow keys
- if (keyCode == 37 || keyCode == 39) {
- removeCaretContainer(getParentCaretContainer(selection.getStart()));
- }
- unmarkBogusCaretParents();
- };
- // Remove bogus state if they got filled by contents using editor.selection.setContent
- ed.on('SetContent', function(e) {
- if (e.selection) {
- unmarkBogusCaretParents();
- }
- });
- ed._hasCaretEvents = true;
- }
- // Do apply or remove caret format
- if (type == "apply") {
- applyCaretFormat();
- } else {
- removeCaretFormat();
- }
- }
- /**
- * Moves the start to the first suitable text node.
- */
- function moveStart(rng) {
- var container = rng.startContainer,
- offset = rng.startOffset, isAtEndOfText,
- walker, node, nodes, tmpNode;
- if (rng.startContainer == rng.endContainer) {
- if (isInlineBlock(rng.startContainer.childNodes[rng.startOffset])) {
- return;
- }
- }
- // Convert text node into index if possible
- if (container.nodeType == 3 && offset >= container.nodeValue.length) {
- // Get the parent container location and walk from there
- offset = nodeIndex(container);
- container = container.parentNode;
- isAtEndOfText = true;
- }
- // Move startContainer/startOffset in to a suitable node
- if (container.nodeType == 1) {
- nodes = container.childNodes;
- container = nodes[Math.min(offset, nodes.length - 1)];
- walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
- // If offset is at end of the parent node walk to the next one
- if (offset > nodes.length - 1 || isAtEndOfText) {
- walker.next();
- }
- for (node = walker.current(); node; node = walker.next()) {
- if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
- // IE has a "neat" feature where it moves the start node into the closest element
- // we can avoid this by inserting an element before it and then remove it after we set the selection
- tmpNode = dom.create('a', {'data-mce-bogus': 'all'}, INVISIBLE_CHAR);
- node.parentNode.insertBefore(tmpNode, node);
- // Set selection and remove tmpNode
- rng.setStart(node, 0);
- selection.setRng(rng);
- dom.remove(tmpNode);
- return;
- }
- }
- }
- }
- };
- });
- // Included from: js/tinymce/classes/UndoManager.js
- /**
- * UndoManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles the undo/redo history levels for the editor. Since the built-in undo/redo has major drawbacks a custom one was needed.
- *
- * @class tinymce.UndoManager
- */
- define("tinymce/UndoManager", [
- "tinymce/util/VK",
- "tinymce/Env"
- ], function(VK, Env) {
- return function(editor) {
- var self = this, index = 0, data = [], beforeBookmark, isFirstTypedCharacter, locks = 0;
- function getContent() {
- return editor.serializer.getTrimmedContent();
- }
- function setDirty(state) {
- editor.setDirty(state);
- }
- function addNonTypingUndoLevel(e) {
- self.typing = false;
- self.add({}, e);
- }
- // Add initial undo level when the editor is initialized
- editor.on('init', function() {
- self.add();
- });
- // Get position before an execCommand is processed
- editor.on('BeforeExecCommand', function(e) {
- var cmd = e.command;
- if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
- self.beforeChange();
- }
- });
- // Add undo level after an execCommand call was made
- editor.on('ExecCommand', function(e) {
- var cmd = e.command;
- if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
- addNonTypingUndoLevel(e);
- }
- });
- editor.on('ObjectResizeStart Cut', function() {
- self.beforeChange();
- });
- editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
- editor.on('DragEnd', addNonTypingUndoLevel);
- editor.on('KeyUp', function(e) {
- var keyCode = e.keyCode;
- // If key is prevented then don't add undo level
- // This would happen on keyboard shortcuts for example
- if (e.isDefaultPrevented()) {
- return;
- }
- if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
- addNonTypingUndoLevel();
- editor.nodeChanged();
- }
- if (keyCode == 46 || keyCode == 8 || (Env.mac && (keyCode == 91 || keyCode == 93))) {
- editor.nodeChanged();
- }
- // Fire a TypingUndo event on the first character entered
- if (isFirstTypedCharacter && self.typing) {
- // Make it dirty if the content was changed after typing the first character
- if (!editor.isDirty()) {
- setDirty(data[0] && getContent() != data[0].content);
- // Fire initial change event
- if (editor.isDirty()) {
- editor.fire('change', {level: data[0], lastLevel: null});
- }
- }
- editor.fire('TypingUndo');
- isFirstTypedCharacter = false;
- editor.nodeChanged();
- }
- });
- editor.on('KeyDown', function(e) {
- var keyCode = e.keyCode;
- // If key is prevented then don't add undo level
- // This would happen on keyboard shortcuts for example
- if (e.isDefaultPrevented()) {
- return;
- }
- // Is character position keys left,right,up,down,home,end,pgdown,pgup,enter
- if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
- if (self.typing) {
- addNonTypingUndoLevel(e);
- }
- return;
- }
- // If key isn't Ctrl+Alt/AltGr
- var modKey = (e.ctrlKey && !e.altKey) || e.metaKey;
- if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing && !modKey) {
- self.beforeChange();
- self.typing = true;
- self.add({}, e);
- isFirstTypedCharacter = true;
- }
- });
- editor.on('MouseDown', function(e) {
- if (self.typing) {
- addNonTypingUndoLevel(e);
- }
- });
- // Add keyboard shortcuts for undo/redo keys
- editor.addShortcut('meta+z', '', 'Undo');
- editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
- editor.on('AddUndo Undo Redo ClearUndos', function(e) {
- if (!e.isDefaultPrevented()) {
- editor.nodeChanged();
- }
- });
- /*eslint consistent-this:0 */
- self = {
- // Explode for debugging reasons
- data: data,
- /**
- * State if the user is currently typing or not. This will add a typing operation into one undo
- * level instead of one new level for each keystroke.
- *
- * @field {Boolean} typing
- */
- typing: false,
- /**
- * Stores away a bookmark to be used when performing an undo action so that the selection is before
- * the change has been made.
- *
- * @method beforeChange
- */
- beforeChange: function() {
- if (!locks) {
- beforeBookmark = editor.selection.getBookmark(2, true);
- }
- },
- /**
- * Adds a new undo level/snapshot to the undo list.
- *
- * @method add
- * @param {Object} level Optional undo level object to add.
- * @param {DOMEvent} event Optional event responsible for the creation of the undo level.
- * @return {Object} Undo level that got added or null it a level wasn't needed.
- */
- add: function(level, event) {
- var i, settings = editor.settings, lastLevel;
- level = level || {};
- level.content = getContent();
- if (locks || editor.removed) {
- return null;
- }
- lastLevel = data[index];
- if (editor.fire('BeforeAddUndo', {level: level, lastLevel: lastLevel, originalEvent: event}).isDefaultPrevented()) {
- return null;
- }
- // Add undo level if needed
- if (lastLevel && lastLevel.content == level.content) {
- return null;
- }
- // Set before bookmark on previous level
- if (data[index]) {
- data[index].beforeBookmark = beforeBookmark;
- }
- // Time to compress
- if (settings.custom_undo_redo_levels) {
- if (data.length > settings.custom_undo_redo_levels) {
- for (i = 0; i < data.length - 1; i++) {
- data[i] = data[i + 1];
- }
- data.length--;
- index = data.length;
- }
- }
- // Get a non intrusive normalized bookmark
- level.bookmark = editor.selection.getBookmark(2, true);
- // Crop array if needed
- if (index < data.length - 1) {
- data.length = index + 1;
- }
- data.push(level);
- index = data.length - 1;
- var args = {level: level, lastLevel: lastLevel, originalEvent: event};
- editor.fire('AddUndo', args);
- if (index > 0) {
- setDirty(true);
- editor.fire('change', args);
- }
- return level;
- },
- /**
- * Undoes the last action.
- *
- * @method undo
- * @return {Object} Undo level or null if no undo was performed.
- */
- undo: function() {
- var level;
- if (self.typing) {
- self.add();
- self.typing = false;
- }
- if (index > 0) {
- level = data[--index];
- editor.setContent(level.content, {format: 'raw'});
- editor.selection.moveToBookmark(level.beforeBookmark);
- setDirty(true);
- editor.fire('undo', {level: level});
- }
- return level;
- },
- /**
- * Redoes the last action.
- *
- * @method redo
- * @return {Object} Redo level or null if no redo was performed.
- */
- redo: function() {
- var level;
- if (index < data.length - 1) {
- level = data[++index];
- editor.setContent(level.content, {format: 'raw'});
- editor.selection.moveToBookmark(level.bookmark);
- setDirty(true);
- editor.fire('redo', {level: level});
- }
- return level;
- },
- /**
- * Removes all undo levels.
- *
- * @method clear
- */
- clear: function() {
- data = [];
- index = 0;
- self.typing = false;
- self.data = data;
- editor.fire('ClearUndos');
- },
- /**
- * Returns true/false if the undo manager has any undo levels.
- *
- * @method hasUndo
- * @return {Boolean} true/false if the undo manager has any undo levels.
- */
- hasUndo: function() {
- // Has undo levels or typing and content isn't the same as the initial level
- return index > 0 || (self.typing && data[0] && getContent() != data[0].content);
- },
- /**
- * Returns true/false if the undo manager has any redo levels.
- *
- * @method hasRedo
- * @return {Boolean} true/false if the undo manager has any redo levels.
- */
- hasRedo: function() {
- return index < data.length - 1 && !this.typing;
- },
- /**
- * Executes the specified mutator function as an undo transaction. The selection
- * before the modification will be stored to the undo stack and if the DOM changes
- * it will add a new undo level. Any methods within the translation that adds undo levels will
- * be ignored. So a translation can include calls to execCommand or editor.insertContent.
- *
- * @method transact
- * @param {function} callback Function that gets executed and has dom manipulation logic in it.
- * @return {Object} Undo level that got added or null it a level wasn't needed.
- */
- transact: function(callback) {
- self.beforeChange();
- try {
- locks++;
- callback();
- } finally {
- locks--;
- }
- return self.add();
- },
- /**
- * Adds an extra "hidden" undo level by first applying the first mutation and store that to the undo stack
- * then roll back that change and do the second mutation on top of the stack. This will produce an extra
- * undo level that the user doesn't see until they undo.
- *
- * @method extra
- * @param {function} callback1 Function that does mutation but gets stored as a "hidden" extra undo level.
- * @param {function} callback2 Function that does mutation but gets displayed to the user.
- */
- extra: function (callback1, callback2) {
- var lastLevel, bookmark;
- if (self.transact(callback1)) {
- bookmark = data[index].bookmark;
- lastLevel = data[index - 1];
- editor.setContent(lastLevel.content, {format: 'raw'});
- editor.selection.moveToBookmark(lastLevel.beforeBookmark);
- if (self.transact(callback2)) {
- data[index - 1].beforeBookmark = bookmark;
- }
- }
- }
- };
- return self;
- };
- });
- // Included from: js/tinymce/classes/EnterKey.js
- /**
- * EnterKey.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Contains logic for handling the enter key to split/generate block elements.
- *
- * @private
- * @class tinymce.EnterKey
- */
- define("tinymce/EnterKey", [
- "tinymce/dom/TreeWalker",
- "tinymce/dom/RangeUtils",
- "tinymce/Env"
- ], function(TreeWalker, RangeUtils, Env) {
- var isIE = Env.ie && Env.ie < 11;
- return function(editor) {
- var dom = editor.dom, selection = editor.selection, settings = editor.settings;
- var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements(),
- moveCaretBeforeOnEnterElementsMap = schema.getMoveCaretBeforeOnEnterElements();
- function handleEnterKey(evt) {
- var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
- newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
- // Returns true if the block can be split into two blocks or not
- function canSplitBlock(node) {
- return node &&
- dom.isBlock(node) &&
- !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
- !/^(fixed|absolute)/i.test(node.style.position) &&
- dom.getContentEditable(node) !== "true";
- }
- function isTableCell(node) {
- return node && /^(TD|TH|CAPTION)$/.test(node.nodeName);
- }
- // Renders empty block on IE
- function renderBlockOnIE(block) {
- var oldRng;
- if (dom.isBlock(block)) {
- oldRng = selection.getRng();
- block.appendChild(dom.create('span', null, '\u00a0'));
- selection.select(block);
- block.lastChild.outerHTML = '';
- selection.setRng(oldRng);
- }
- }
- // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
- function trimInlineElementsOnLeftSideOfBlock(block) {
- var node = block, firstChilds = [], i;
- if (!node) {
- return;
- }
- // Find inner most first child ex: <p><i><b>*</b></i></p>
- while ((node = node.firstChild)) {
- if (dom.isBlock(node)) {
- return;
- }
- if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
- firstChilds.push(node);
- }
- }
- i = firstChilds.length;
- while (i--) {
- node = firstChilds[i];
- if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
- dom.remove(node);
- } else {
- // Remove <a> </a> see #5381
- if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
- dom.remove(node);
- }
- }
- }
- }
- // Moves the caret to a suitable position within the root for example in the first non
- // pure whitespace text node or before an image
- function moveToCaretPosition(root) {
- var walker, node, rng, lastNode = root, tempElm;
- function firstNonWhiteSpaceNodeSibling(node) {
- while (node) {
- if (node.nodeType == 1 || (node.nodeType == 3 && node.data && /[\r\n\s]/.test(node.data))) {
- return node;
- }
- node = node.nextSibling;
- }
- }
- if (!root) {
- return;
- }
- // Old IE versions doesn't properly render blocks with br elements in them
- // For example <p><br></p> wont be rendered correctly in a contentEditable area
- // until you remove the br producing <p></p>
- if (Env.ie && Env.ie < 9 && parentBlock && parentBlock.firstChild) {
- if (parentBlock.firstChild == parentBlock.lastChild && parentBlock.firstChild.tagName == 'BR') {
- dom.remove(parentBlock.firstChild);
- }
- }
- if (/^(LI|DT|DD)$/.test(root.nodeName)) {
- var firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
- if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
- root.insertBefore(dom.doc.createTextNode('\u00a0'), root.firstChild);
- }
- }
- rng = dom.createRng();
- // Normalize whitespace to remove empty text nodes. Fix for: #6904
- // Gecko will be able to place the caret in empty text nodes but it won't render propery
- // Older IE versions will sometimes crash so for now ignore all IE versions
- if (!Env.ie) {
- root.normalize();
- }
- if (root.hasChildNodes()) {
- walker = new TreeWalker(root, root);
- while ((node = walker.current())) {
- if (node.nodeType == 3) {
- rng.setStart(node, 0);
- rng.setEnd(node, 0);
- break;
- }
- if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
- rng.setStartBefore(node);
- rng.setEndBefore(node);
- break;
- }
- lastNode = node;
- node = walker.next();
- }
- if (!node) {
- rng.setStart(lastNode, 0);
- rng.setEnd(lastNode, 0);
- }
- } else {
- if (root.nodeName == 'BR') {
- if (root.nextSibling && dom.isBlock(root.nextSibling)) {
- // Trick on older IE versions to render the caret before the BR between two lists
- if (!documentMode || documentMode < 9) {
- tempElm = dom.create('br');
- root.parentNode.insertBefore(tempElm, root);
- }
- rng.setStartBefore(root);
- rng.setEndBefore(root);
- } else {
- rng.setStartAfter(root);
- rng.setEndAfter(root);
- }
- } else {
- rng.setStart(root, 0);
- rng.setEnd(root, 0);
- }
- }
- selection.setRng(rng);
- // Remove tempElm created for old IE:s
- dom.remove(tempElm);
- selection.scrollIntoView(root);
- }
- function setForcedBlockAttrs(node) {
- var forcedRootBlockName = settings.forced_root_block;
- if (forcedRootBlockName && forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
- dom.setAttribs(node, settings.forced_root_block_attrs);
- }
- }
- function emptyBlock(elm) {
- // BR is needed in empty blocks on non IE browsers
- elm.innerHTML = !isIE ? '<br data-mce-bogus="1">' : '';
- }
- // Creates a new block element by cloning the current one or creating a new one if the name is specified
- // This function will also copy any text formatting from the parent block and add it to the new one
- function createNewBlock(name) {
- var node = container, block, clonedNode, caretNode, textInlineElements = schema.getTextInlineElements();
- if (name || parentBlockName == "TABLE") {
- block = dom.create(name || newBlockName);
- setForcedBlockAttrs(block);
- } else {
- block = parentBlock.cloneNode(false);
- }
- caretNode = block;
- // Clone any parent styles
- if (settings.keep_styles !== false) {
- do {
- if (textInlineElements[node.nodeName]) {
- // Never clone a caret containers
- if (node.id == '_mce_caret') {
- continue;
- }
- clonedNode = node.cloneNode(false);
- dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
- if (block.hasChildNodes()) {
- clonedNode.appendChild(block.firstChild);
- block.appendChild(clonedNode);
- } else {
- caretNode = clonedNode;
- block.appendChild(clonedNode);
- }
- }
- } while ((node = node.parentNode) && node != editableRoot);
- }
- // BR is needed in empty blocks on non IE browsers
- if (!isIE) {
- caretNode.innerHTML = '<br data-mce-bogus="1">';
- }
- return block;
- }
- // Returns true/false if the caret is at the start/end of the parent block element
- function isCaretAtStartOrEndOfBlock(start) {
- var walker, node, name;
- // Caret is in the middle of a text node like "a|b"
- if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
- return false;
- }
- // If after the last element in block node edge case for #5091
- if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
- return true;
- }
- // If the caret if before the first element in parentBlock
- if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
- return true;
- }
- // Caret can be before/after a table
- if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
- return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
- }
- // Walk the DOM and look for text nodes or non empty elements
- walker = new TreeWalker(container, parentBlock);
- // If caret is in beginning or end of a text block then jump to the next/previous node
- if (container.nodeType == 3) {
- if (start && offset === 0) {
- walker.prev();
- } else if (!start && offset == container.nodeValue.length) {
- walker.next();
- }
- }
- while ((node = walker.current())) {
- if (node.nodeType === 1) {
- // Ignore bogus elements
- if (!node.getAttribute('data-mce-bogus')) {
- // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
- name = node.nodeName.toLowerCase();
- if (nonEmptyElementsMap[name] && name !== 'br') {
- return false;
- }
- }
- } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
- return false;
- }
- if (start) {
- walker.prev();
- } else {
- walker.next();
- }
- }
- return true;
- }
- // Wraps any text nodes or inline elements in the specified forced root block name
- function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
- var newBlock, parentBlock, startNode, node, next, rootBlockName, blockName = newBlockName || 'P';
- // Not in a block element or in a table cell or caption
- parentBlock = dom.getParent(container, dom.isBlock);
- if (!parentBlock || !canSplitBlock(parentBlock)) {
- parentBlock = parentBlock || editableRoot;
- if (parentBlock == editor.getBody() || isTableCell(parentBlock)) {
- rootBlockName = parentBlock.nodeName.toLowerCase();
- } else {
- rootBlockName = parentBlock.parentNode.nodeName.toLowerCase();
- }
- if (!parentBlock.hasChildNodes()) {
- newBlock = dom.create(blockName);
- setForcedBlockAttrs(newBlock);
- parentBlock.appendChild(newBlock);
- rng.setStart(newBlock, 0);
- rng.setEnd(newBlock, 0);
- return newBlock;
- }
- // Find parent that is the first child of parentBlock
- node = container;
- while (node.parentNode != parentBlock) {
- node = node.parentNode;
- }
- // Loop left to find start node start wrapping at
- while (node && !dom.isBlock(node)) {
- startNode = node;
- node = node.previousSibling;
- }
- if (startNode && schema.isValidChild(rootBlockName, blockName.toLowerCase())) {
- newBlock = dom.create(blockName);
- setForcedBlockAttrs(newBlock);
- startNode.parentNode.insertBefore(newBlock, startNode);
- // Start wrapping until we hit a block
- node = startNode;
- while (node && !dom.isBlock(node)) {
- next = node.nextSibling;
- newBlock.appendChild(node);
- node = next;
- }
- // Restore range to it's past location
- rng.setStart(container, offset);
- rng.setEnd(container, offset);
- }
- }
- return container;
- }
- // Inserts a block or br before/after or in the middle of a split list of the LI is empty
- function handleEmptyListItem() {
- function isFirstOrLastLi(first) {
- var node = containerBlock[first ? 'firstChild' : 'lastChild'];
- // Find first/last element since there might be whitespace there
- while (node) {
- if (node.nodeType == 1) {
- break;
- }
- node = node[first ? 'nextSibling' : 'previousSibling'];
- }
- return node === parentBlock;
- }
- function getContainerBlock() {
- var containerBlockParent = containerBlock.parentNode;
- if (/^(LI|DT|DD)$/.test(containerBlockParent.nodeName)) {
- return containerBlockParent;
- }
- return containerBlock;
- }
- if (containerBlock == editor.getBody()) {
- return;
- }
- // Check if we are in an nested list
- var containerBlockParentName = containerBlock.parentNode.nodeName;
- if (/^(OL|UL|LI)$/.test(containerBlockParentName)) {
- newBlockName = 'LI';
- }
- newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
- if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
- if (containerBlockParentName == 'LI') {
- // Nested list is inside a LI
- dom.insertAfter(newBlock, getContainerBlock());
- } else {
- // Is first and last list item then replace the OL/UL with a text block
- dom.replace(newBlock, containerBlock);
- }
- } else if (isFirstOrLastLi(true)) {
- if (containerBlockParentName == 'LI') {
- // List nested in an LI then move the list to a new sibling LI
- dom.insertAfter(newBlock, getContainerBlock());
- newBlock.appendChild(dom.doc.createTextNode(' ')); // Needed for IE so the caret can be placed
- newBlock.appendChild(containerBlock);
- } else {
- // First LI in list then remove LI and add text block before list
- containerBlock.parentNode.insertBefore(newBlock, containerBlock);
- }
- } else if (isFirstOrLastLi()) {
- // Last LI in list then remove LI and add text block after list
- dom.insertAfter(newBlock, getContainerBlock());
- renderBlockOnIE(newBlock);
- } else {
- // Middle LI in list the split the list and insert a text block in the middle
- // Extract after fragment and insert it after the current block
- containerBlock = getContainerBlock();
- tmpRng = rng.cloneRange();
- tmpRng.setStartAfter(parentBlock);
- tmpRng.setEndAfter(containerBlock);
- fragment = tmpRng.extractContents();
- if (newBlockName == 'LI' && fragment.firstChild.nodeName == 'LI') {
- newBlock = fragment.firstChild;
- dom.insertAfter(fragment, containerBlock);
- } else {
- dom.insertAfter(fragment, containerBlock);
- dom.insertAfter(newBlock, containerBlock);
- }
- }
- dom.remove(parentBlock);
- moveToCaretPosition(newBlock);
- undoManager.add();
- }
- // Inserts a BR element if the forced_root_block option is set to false or empty string
- function insertBr() {
- editor.execCommand("InsertLineBreak", false, evt);
- }
- // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
- function trimLeadingLineBreaks(node) {
- do {
- if (node.nodeType === 3) {
- node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
- }
- node = node.firstChild;
- } while (node);
- }
- function getEditableRoot(node) {
- var root = dom.getRoot(), parent, editableRoot;
- // Get all parents until we hit a non editable parent or the root
- parent = node;
- while (parent !== root && dom.getContentEditable(parent) !== "false") {
- if (dom.getContentEditable(parent) === "true") {
- editableRoot = parent;
- }
- parent = parent.parentNode;
- }
- return parent !== root ? editableRoot : root;
- }
- // Adds a BR at the end of blocks that only contains an IMG or INPUT since
- // these might be floated and then they won't expand the block
- function addBrToBlockIfNeeded(block) {
- var lastChild;
- // IE will render the blocks correctly other browsers needs a BR
- if (!isIE) {
- block.normalize(); // Remove empty text nodes that got left behind by the extract
- // Check if the block is empty or contains a floated last child
- lastChild = block.lastChild;
- if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
- dom.add(block, 'br');
- }
- }
- }
- function insertNewBlockAfter() {
- // 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
- if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
- newBlock = createNewBlock(newBlockName);
- } else {
- newBlock = createNewBlock();
- }
- // Split the current container block element if enter is pressed inside an empty inner block element
- if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
- // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
- newBlock = dom.split(containerBlock, parentBlock);
- } else {
- dom.insertAfter(newBlock, parentBlock);
- }
- moveToCaretPosition(newBlock);
- }
- rng = selection.getRng(true);
- // Event is blocked by some other handler for example the lists plugin
- if (evt.isDefaultPrevented()) {
- return;
- }
- // Delete any selected contents
- if (!rng.collapsed) {
- editor.execCommand('Delete');
- return;
- }
- // Setup range items and newBlockName
- new RangeUtils(dom).normalize(rng);
- container = rng.startContainer;
- offset = rng.startOffset;
- newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
- newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
- documentMode = dom.doc.documentMode;
- shiftKey = evt.shiftKey;
- // Resolve node index
- if (container.nodeType == 1 && container.hasChildNodes()) {
- isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
- container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
- if (isAfterLastNodeInContainer && container.nodeType == 3) {
- offset = container.nodeValue.length;
- } else {
- offset = 0;
- }
- }
- // Get editable root node, normally the body element but sometimes a div or span
- editableRoot = getEditableRoot(container);
- // If there is no editable root then enter is done inside a contentEditable false element
- if (!editableRoot) {
- return;
- }
- undoManager.beforeChange();
- // If editable root isn't block nor the root of the editor
- if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
- if (!newBlockName || shiftKey) {
- insertBr();
- }
- return;
- }
- // Wrap the current node and it's sibling in a default block if it's needed.
- // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
- // This won't happen if root blocks are disabled or the shiftKey is pressed
- if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
- container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
- }
- // Find parent block and setup empty block paddings
- parentBlock = dom.getParent(container, dom.isBlock);
- containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
- // Setup block names
- parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
- containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
- // Enter inside block contained within a LI then split or insert before/after LI
- if (containerBlockName == 'LI' && !evt.ctrlKey) {
- parentBlock = containerBlock;
- parentBlockName = containerBlockName;
- }
- // Handle enter in list item
- if (/^(LI|DT|DD)$/.test(parentBlockName)) {
- if (!newBlockName && shiftKey) {
- insertBr();
- return;
- }
- // Handle enter inside an empty list item
- if (dom.isEmpty(parentBlock)) {
- handleEmptyListItem();
- return;
- }
- }
- // Don't split PRE tags but insert a BR instead easier when writing code samples etc
- if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
- if (!shiftKey) {
- insertBr();
- return;
- }
- } else {
- // If no root block is configured then insert a BR by default or if the shiftKey is pressed
- if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
- insertBr();
- return;
- }
- }
- // If parent block is root then never insert new blocks
- if (newBlockName && parentBlock === editor.getBody()) {
- return;
- }
- // Default block name if it's not configured
- newBlockName = newBlockName || 'P';
- // Insert new block before/after the parent block depending on caret location
- if (isCaretAtStartOrEndOfBlock()) {
- insertNewBlockAfter();
- } else if (isCaretAtStartOrEndOfBlock(true)) {
- // Insert new block before
- newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
- renderBlockOnIE(newBlock);
- moveToCaretPosition(parentBlock);
- } else {
- // Extract after fragment and insert it after the current block
- tmpRng = rng.cloneRange();
- tmpRng.setEndAfter(parentBlock);
- fragment = tmpRng.extractContents();
- trimLeadingLineBreaks(fragment);
- newBlock = fragment.firstChild;
- dom.insertAfter(fragment, parentBlock);
- trimInlineElementsOnLeftSideOfBlock(newBlock);
- addBrToBlockIfNeeded(parentBlock);
- if (dom.isEmpty(parentBlock)) {
- emptyBlock(parentBlock);
- }
- newBlock.normalize();
- // New block might become empty if it's <p><b>a |</b></p>
- if (dom.isEmpty(newBlock)) {
- dom.remove(newBlock);
- insertNewBlockAfter();
- } else {
- moveToCaretPosition(newBlock);
- }
- }
- dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
- // Allow custom handling of new blocks
- editor.fire('NewBlock', {newBlock: newBlock});
- undoManager.add();
- }
- editor.on('keydown', function(evt) {
- if (evt.keyCode == 13) {
- if (handleEnterKey(evt) !== false) {
- evt.preventDefault();
- }
- }
- });
- };
- });
- // Included from: js/tinymce/classes/ForceBlocks.js
- /**
- * ForceBlocks.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Makes sure that everything gets wrapped in paragraphs.
- *
- * @private
- * @class tinymce.ForceBlocks
- */
- define("tinymce/ForceBlocks", [], function() {
- return function(editor) {
- var settings = editor.settings, dom = editor.dom, selection = editor.selection;
- var schema = editor.schema, blockElements = schema.getBlockElements();
- function addRootBlocks() {
- var node = selection.getStart(), rootNode = editor.getBody(), rng;
- var startContainer, startOffset, endContainer, endOffset, rootBlockNode;
- var tempNode, offset = -0xFFFFFF, wrapped, restoreSelection;
- var tmpRng, rootNodeName, forcedRootBlock;
- forcedRootBlock = settings.forced_root_block;
- if (!node || node.nodeType !== 1 || !forcedRootBlock) {
- return;
- }
- // Check if node is wrapped in block
- while (node && node != rootNode) {
- if (blockElements[node.nodeName]) {
- return;
- }
- node = node.parentNode;
- }
- // Get current selection
- rng = selection.getRng();
- if (rng.setStart) {
- startContainer = rng.startContainer;
- startOffset = rng.startOffset;
- endContainer = rng.endContainer;
- endOffset = rng.endOffset;
- try {
- restoreSelection = editor.getDoc().activeElement === rootNode;
- } catch (ex) {
- // IE throws unspecified error here sometimes
- }
- } else {
- // Force control range into text range
- if (rng.item) {
- node = rng.item(0);
- rng = editor.getDoc().body.createTextRange();
- rng.moveToElementText(node);
- }
- restoreSelection = rng.parentElement().ownerDocument === editor.getDoc();
- tmpRng = rng.duplicate();
- tmpRng.collapse(true);
- startOffset = tmpRng.move('character', offset) * -1;
- if (!tmpRng.collapsed) {
- tmpRng = rng.duplicate();
- tmpRng.collapse(false);
- endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
- }
- }
- // Wrap non block elements and text nodes
- node = rootNode.firstChild;
- rootNodeName = rootNode.nodeName.toLowerCase();
- while (node) {
- // TODO: Break this up, too complex
- if (((node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName]))) &&
- schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase())) {
- // Remove empty text nodes
- if (node.nodeType === 3 && node.nodeValue.length === 0) {
- tempNode = node;
- node = node.nextSibling;
- dom.remove(tempNode);
- continue;
- }
- if (!rootBlockNode) {
- rootBlockNode = dom.create(forcedRootBlock, editor.settings.forced_root_block_attrs);
- node.parentNode.insertBefore(rootBlockNode, node);
- wrapped = true;
- }
- tempNode = node;
- node = node.nextSibling;
- rootBlockNode.appendChild(tempNode);
- } else {
- rootBlockNode = null;
- node = node.nextSibling;
- }
- }
- if (wrapped && restoreSelection) {
- if (rng.setStart) {
- rng.setStart(startContainer, startOffset);
- rng.setEnd(endContainer, endOffset);
- selection.setRng(rng);
- } else {
- // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
- try {
- rng = editor.getDoc().body.createTextRange();
- rng.moveToElementText(rootNode);
- rng.collapse(true);
- rng.moveStart('character', startOffset);
- if (endOffset > 0) {
- rng.moveEnd('character', endOffset);
- }
- rng.select();
- } catch (ex) {
- // Ignore
- }
- }
- editor.nodeChanged();
- }
- }
- // Force root blocks
- if (settings.forced_root_block) {
- editor.on('NodeChange', addRootBlocks);
- }
- };
- });
- // Included from: js/tinymce/classes/caret/CaretUtils.js
- /**
- * CaretUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility functions shared by the caret logic.
- *
- * @private
- * @class tinymce.caret.CaretUtils
- */
- define("tinymce/caret/CaretUtils", [
- "tinymce/util/Fun",
- "tinymce/dom/TreeWalker",
- "tinymce/dom/NodeType",
- "tinymce/caret/CaretPosition",
- "tinymce/caret/CaretContainer",
- "tinymce/caret/CaretCandidate"
- ], function(Fun, TreeWalker, NodeType, CaretPosition, CaretContainer, CaretCandidate) {
- var isContentEditableTrue = NodeType.isContentEditableTrue,
- isContentEditableFalse = NodeType.isContentEditableFalse,
- isBlockLike = NodeType.matchStyleValues('display', 'block table table-cell table-caption'),
- isCaretContainer = CaretContainer.isCaretContainer,
- curry = Fun.curry,
- isElement = NodeType.isElement,
- isCaretCandidate = CaretCandidate.isCaretCandidate;
- function isForwards(direction) {
- return direction > 0;
- }
- function isBackwards(direction) {
- return direction < 0;
- }
- function findNode(node, direction, predicateFn, rootNode, shallow) {
- var walker = new TreeWalker(node, rootNode);
- if (isBackwards(direction)) {
- if (isContentEditableFalse(node)) {
- node = walker.prev(true);
- if (predicateFn(node)) {
- return node;
- }
- }
- while ((node = walker.prev(shallow))) {
- if (predicateFn(node)) {
- return node;
- }
- }
- }
- if (isForwards(direction)) {
- if (isContentEditableFalse(node)) {
- node = walker.next(true);
- if (predicateFn(node)) {
- return node;
- }
- }
- while ((node = walker.next(shallow))) {
- if (predicateFn(node)) {
- return node;
- }
- }
- }
- return null;
- }
- function getEditingHost(node, rootNode) {
- for (node = node.parentNode; node && node != rootNode; node = node.parentNode) {
- if (isContentEditableTrue(node)) {
- return node;
- }
- }
- return rootNode;
- }
- function getParentBlock(node, rootNode) {
- while (node && node != rootNode) {
- if (isBlockLike(node)) {
- return node;
- }
- node = node.parentNode;
- }
- return null;
- }
- function isInSameBlock(caretPosition1, caretPosition2, rootNode) {
- return getParentBlock(caretPosition1.container(), rootNode) == getParentBlock(caretPosition2.container(), rootNode);
- }
- function isInSameEditingHost(caretPosition1, caretPosition2, rootNode) {
- return getEditingHost(caretPosition1.container(), rootNode) == getEditingHost(caretPosition2.container(), rootNode);
- }
- function getChildNodeAtRelativeOffset(relativeOffset, caretPosition) {
- var container, offset;
- if (!caretPosition) {
- return null;
- }
- container = caretPosition.container();
- offset = caretPosition.offset();
- if (!isElement(container)) {
- return null;
- }
- return container.childNodes[offset + relativeOffset];
- }
- function beforeAfter(before, node) {
- var range = node.ownerDocument.createRange();
- if (before) {
- range.setStartBefore(node);
- range.setEndBefore(node);
- } else {
- range.setStartAfter(node);
- range.setEndAfter(node);
- }
- return range;
- }
- function isNodesInSameBlock(rootNode, node1, node2) {
- return getParentBlock(node1, rootNode) == getParentBlock(node2, rootNode);
- }
- function lean(left, rootNode, node) {
- var sibling, siblingName;
- if (left) {
- siblingName = 'previousSibling';
- } else {
- siblingName = 'nextSibling';
- }
- while (node && node != rootNode) {
- sibling = node[siblingName];
- if (isCaretContainer(sibling)) {
- sibling = sibling[siblingName];
- }
- if (isContentEditableFalse(sibling)) {
- if (isNodesInSameBlock(rootNode, sibling, node)) {
- return sibling;
- }
- break;
- }
- if (isCaretCandidate(sibling)) {
- break;
- }
- node = node.parentNode;
- }
- return null;
- }
- var before = curry(beforeAfter, true);
- var after = curry(beforeAfter, false);
- function normalizeRange(direction, rootNode, range) {
- var node, container, offset, location;
- var leanLeft = curry(lean, true, rootNode);
- var leanRight = curry(lean, false, rootNode);
- container = range.startContainer;
- offset = range.startOffset;
- if (CaretContainer.isCaretContainerBlock(container)) {
- if (!isElement(container)) {
- container = container.parentNode;
- }
- location = container.getAttribute('data-mce-caret');
- if (location == 'before') {
- node = container.nextSibling;
- if (isContentEditableFalse(node)) {
- return before(node);
- }
- }
- if (location == 'after') {
- node = container.previousSibling;
- if (isContentEditableFalse(node)) {
- return after(node);
- }
- }
- }
- if (!range.collapsed) {
- return range;
- }
- if (NodeType.isText(container)) {
- if (isCaretContainer(container)) {
- if (direction === 1) {
- node = leanRight(container);
- if (node) {
- return before(node);
- }
- node = leanLeft(container);
- if (node) {
- return after(node);
- }
- }
- if (direction === -1) {
- node = leanLeft(container);
- if (node) {
- return after(node);
- }
- node = leanRight(container);
- if (node) {
- return before(node);
- }
- }
- return range;
- }
- if (CaretContainer.endsWithCaretContainer(container) && offset >= container.data.length - 1) {
- if (direction === 1) {
- node = leanRight(container);
- if (node) {
- return before(node);
- }
- }
- return range;
- }
- if (CaretContainer.startsWithCaretContainer(container) && offset <= 1) {
- if (direction === -1) {
- node = leanLeft(container);
- if (node) {
- return after(node);
- }
- }
- return range;
- }
- if (offset === container.data.length) {
- node = leanRight(container);
- if (node) {
- return before(node);
- }
- return range;
- }
- if (offset === 0) {
- node = leanLeft(container);
- if (node) {
- return after(node);
- }
- return range;
- }
- }
- return range;
- }
- function isNextToContentEditableFalse(relativeOffset, caretPosition) {
- return isContentEditableFalse(getChildNodeAtRelativeOffset(relativeOffset, caretPosition));
- }
- return {
- isForwards: isForwards,
- isBackwards: isBackwards,
- findNode: findNode,
- getEditingHost: getEditingHost,
- getParentBlock: getParentBlock,
- isInSameBlock: isInSameBlock,
- isInSameEditingHost: isInSameEditingHost,
- isBeforeContentEditableFalse: curry(isNextToContentEditableFalse, 0),
- isAfterContentEditableFalse: curry(isNextToContentEditableFalse, -1),
- normalizeRange: normalizeRange
- };
- });
- // Included from: js/tinymce/classes/caret/CaretWalker.js
- /**
- * CaretWalker.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module contains logic for moving around a virtual caret in logical order within a DOM element.
- *
- * It ignores the most obvious invalid caret locations such as within a script element or within a
- * contentEditable=false element but it will return locations that isn't possible to render visually.
- *
- * @private
- * @class tinymce.caret.CaretWalker
- * @example
- * var caretWalker = new CaretWalker(rootElm);
- *
- * var prevLogicalCaretPosition = caretWalker.prev(CaretPosition.fromRangeStart(range));
- * var nextLogicalCaretPosition = caretWalker.next(CaretPosition.fromRangeEnd(range));
- */
- define("tinymce/caret/CaretWalker", [
- "tinymce/dom/NodeType",
- "tinymce/caret/CaretCandidate",
- "tinymce/caret/CaretPosition",
- "tinymce/caret/CaretUtils",
- "tinymce/util/Arr",
- "tinymce/util/Fun"
- ], function(NodeType, CaretCandidate, CaretPosition, CaretUtils, Arr, Fun) {
- var isContentEditableFalse = NodeType.isContentEditableFalse,
- isText = NodeType.isText,
- isElement = NodeType.isElement,
- isBr = NodeType.isBr,
- isForwards = CaretUtils.isForwards,
- isBackwards = CaretUtils.isBackwards,
- isCaretCandidate = CaretCandidate.isCaretCandidate,
- isAtomic = CaretCandidate.isAtomic,
- isEditableCaretCandidate = CaretCandidate.isEditableCaretCandidate;
- function getParents(node, rootNode) {
- var parents = [];
- while (node && node != rootNode) {
- parents.push(node);
- node = node.parentNode;
- }
- return parents;
- }
- function nodeAtIndex(container, offset) {
- if (container.hasChildNodes() && offset < container.childNodes.length) {
- return container.childNodes[offset];
- }
- return null;
- }
- function getCaretCandidatePosition(direction, node) {
- if (isForwards(direction)) {
- if (isCaretCandidate(node.previousSibling) && !isText(node.previousSibling)) {
- return CaretPosition.before(node);
- }
- if (isText(node)) {
- return CaretPosition(node, 0);
- }
- }
- if (isBackwards(direction)) {
- if (isCaretCandidate(node.nextSibling) && !isText(node.nextSibling)) {
- return CaretPosition.after(node);
- }
- if (isText(node)) {
- return CaretPosition(node, node.data.length);
- }
- }
- if (isBackwards(direction)) {
- if (isBr(node)) {
- return CaretPosition.before(node);
- }
- return CaretPosition.after(node);
- }
- return CaretPosition.before(node);
- }
- // Jumps over BR elements <p>|<br></p><p>a</p> -> <p><br></p><p>|a</p>
- function isBrBeforeBlock(node, rootNode) {
- var next;
- if (!NodeType.isBr(node)) {
- return false;
- }
- next = findCaretPosition(1, CaretPosition.after(node), rootNode);
- if (!next) {
- return false;
- }
- return !CaretUtils.isInSameBlock(CaretPosition.before(node), CaretPosition.before(next), rootNode);
- }
- function findCaretPosition(direction, startCaretPosition, rootNode) {
- var container, offset, node, nextNode, innerNode,
- rootContentEditableFalseElm, caretPosition;
- if (!isElement(rootNode) || !startCaretPosition) {
- return null;
- }
- caretPosition = startCaretPosition;
- container = caretPosition.container();
- offset = caretPosition.offset();
- if (isText(container)) {
- if (isBackwards(direction) && offset > 0) {
- return CaretPosition(container, --offset);
- }
- if (isForwards(direction) && offset < container.length) {
- return CaretPosition(container, ++offset);
- }
- node = container;
- } else {
- if (isBackwards(direction) && offset > 0) {
- nextNode = nodeAtIndex(container, offset - 1);
- if (isCaretCandidate(nextNode)) {
- if (!isAtomic(nextNode)) {
- innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
- if (innerNode) {
- if (isText(innerNode)) {
- return CaretPosition(innerNode, innerNode.data.length);
- }
- return CaretPosition.after(innerNode);
- }
- }
- if (isText(nextNode)) {
- return CaretPosition(nextNode, nextNode.data.length);
- }
- return CaretPosition.before(nextNode);
- }
- }
- if (isForwards(direction) && offset < container.childNodes.length) {
- nextNode = nodeAtIndex(container, offset);
- if (isCaretCandidate(nextNode)) {
- if (isBrBeforeBlock(nextNode, rootNode)) {
- return findCaretPosition(direction, CaretPosition.after(nextNode), rootNode);
- }
- if (!isAtomic(nextNode)) {
- innerNode = CaretUtils.findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
- if (innerNode) {
- if (isText(innerNode)) {
- return CaretPosition(innerNode, 0);
- }
- return CaretPosition.before(innerNode);
- }
- }
- if (isText(nextNode)) {
- return CaretPosition(nextNode, 0);
- }
- return CaretPosition.after(nextNode);
- }
- }
- node = caretPosition.getNode();
- }
- if ((isForwards(direction) && caretPosition.isAtEnd()) || (isBackwards(direction) && caretPosition.isAtStart())) {
- node = CaretUtils.findNode(node, direction, Fun.constant(true), rootNode, true);
- if (isEditableCaretCandidate(node)) {
- return getCaretCandidatePosition(direction, node);
- }
- }
- nextNode = CaretUtils.findNode(node, direction, isEditableCaretCandidate, rootNode);
- rootContentEditableFalseElm = Arr.last(Arr.filter(getParents(container, rootNode), isContentEditableFalse));
- if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
- if (isForwards(direction)) {
- caretPosition = CaretPosition.after(rootContentEditableFalseElm);
- } else {
- caretPosition = CaretPosition.before(rootContentEditableFalseElm);
- }
- return caretPosition;
- }
- if (nextNode) {
- return getCaretCandidatePosition(direction, nextNode);
- }
- return null;
- }
- return function(rootNode) {
- return {
- /**
- * Returns the next logical caret position from the specificed input
- * caretPoisiton or null if there isn't any more positions left for example
- * at the end specified root element.
- *
- * @method next
- * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
- * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
- */
- next: function(caretPosition) {
- return findCaretPosition(1, caretPosition, rootNode);
- },
- /**
- * Returns the previous logical caret position from the specificed input
- * caretPoisiton or null if there isn't any more positions left for example
- * at the end specified root element.
- *
- * @method prev
- * @param {tinymce.caret.CaretPosition} caretPosition Caret position to start from.
- * @return {tinymce.caret.CaretPosition} CaretPosition or null if no position was found.
- */
- prev: function(caretPosition) {
- return findCaretPosition(-1, caretPosition, rootNode);
- }
- };
- };
- });
- // Included from: js/tinymce/classes/InsertList.js
- /**
- * InsertList.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Handles inserts of lists into the editor instance.
- *
- * @class tinymce.InsertList
- * @private
- */
- define("tinymce/InsertList", [
- "tinymce/util/Tools",
- "tinymce/caret/CaretWalker",
- "tinymce/caret/CaretPosition"
- ], function(Tools, CaretWalker, CaretPosition) {
- var isListFragment = function(fragment) {
- var firstChild = fragment.firstChild;
- var lastChild = fragment.lastChild;
- // Skip meta since it's likely <meta><ul>..</ul>
- if (firstChild && firstChild.name === 'meta') {
- firstChild = firstChild.next;
- }
- // Skip mce_marker since it's likely <ul>..</ul><span id="mce_marker"></span>
- if (lastChild && lastChild.attr('id') === 'mce_marker') {
- lastChild = lastChild.prev;
- }
- if (!firstChild || firstChild !== lastChild) {
- return false;
- }
- return firstChild.name === 'ul' || firstChild.name === 'ol';
- };
- var cleanupDomFragment = function (domFragment) {
- var firstChild = domFragment.firstChild;
- var lastChild = domFragment.lastChild;
- // TODO: remove the meta tag from paste logic
- if (firstChild && firstChild.nodeName === 'META') {
- firstChild.parentNode.removeChild(firstChild);
- }
- if (lastChild && lastChild.id === 'mce_marker') {
- lastChild.parentNode.removeChild(lastChild);
- }
- return domFragment;
- };
- var toDomFragment = function(dom, serializer, fragment) {
- var html = serializer.serialize(fragment);
- var domFragment = dom.createFragment(html);
- return cleanupDomFragment(domFragment);
- };
- var listItems = function(elm) {
- return Tools.grep(elm.childNodes, function(child) {
- return child.nodeName === 'LI';
- });
- };
- var isEmpty = function (elm) {
- return !elm.firstChild;
- };
- var trimListItems = function(elms) {
- return elms.length > 0 && isEmpty(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;
- };
- var getParentLi = function(dom, node) {
- var parentBlock = dom.getParent(node, dom.isBlock);
- return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null;
- };
- var isParentBlockLi = function(dom, node) {
- return !!getParentLi(dom, node);
- };
- var getSplit = function(parentNode, rng) {
- var beforeRng = rng.cloneRange();
- var afterRng = rng.cloneRange();
- beforeRng.setStartBefore(parentNode);
- afterRng.setEndAfter(parentNode);
- return [
- beforeRng.cloneContents(),
- afterRng.cloneContents()
- ];
- };
- var findFirstIn = function(node, rootNode) {
- var caretPos = CaretPosition.before(node);
- var caretWalker = new CaretWalker(rootNode);
- var newCaretPos = caretWalker.next(caretPos);
- return newCaretPos ? newCaretPos.toRange() : null;
- };
- var findLastOf = function(node, rootNode) {
- var caretPos = CaretPosition.after(node);
- var caretWalker = new CaretWalker(rootNode);
- var newCaretPos = caretWalker.prev(caretPos);
- return newCaretPos ? newCaretPos.toRange() : null;
- };
- var insertMiddle = function(target, elms, rootNode, rng) {
- var parts = getSplit(target, rng);
- var parentElm = target.parentNode;
- parentElm.insertBefore(parts[0], target);
- Tools.each(elms, function(li) {
- parentElm.insertBefore(li, target);
- });
- parentElm.insertBefore(parts[1], target);
- parentElm.removeChild(target);
- return findLastOf(elms[elms.length - 1], rootNode);
- };
- var insertBefore = function(target, elms, rootNode) {
- var parentElm = target.parentNode;
- Tools.each(elms, function(elm) {
- parentElm.insertBefore(elm, target);
- });
- return findFirstIn(target, rootNode);
- };
- var insertAfter = function(target, elms, rootNode, dom) {
- dom.insertAfter(elms.reverse(), target);
- return findLastOf(elms[0], rootNode);
- };
- var insertAtCaret = function(serializer, dom, rng, fragment) {
- var domFragment = toDomFragment(dom, serializer, fragment);
- var liTarget = getParentLi(dom, rng.startContainer);
- var liElms = trimListItems(listItems(domFragment.firstChild));
- var BEGINNING = 1, END = 2;
- var rootNode = dom.getRoot();
- var isAt = function(location) {
- var caretPos = CaretPosition.fromRangeStart(rng);
- var caretWalker = new CaretWalker(dom.getRoot());
- var newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);
- return newPos ? getParentLi(dom, newPos.getNode()) !== liTarget : true;
- };
- if (isAt(BEGINNING)) {
- return insertBefore(liTarget, liElms, rootNode);
- } else if (isAt(END)) {
- return insertAfter(liTarget, liElms, rootNode, dom);
- }
- return insertMiddle(liTarget, liElms, rootNode, rng);
- };
- return {
- isListFragment: isListFragment,
- insertAtCaret: insertAtCaret,
- isParentBlockLi: isParentBlockLi,
- trimListItems: trimListItems,
- listItems: listItems
- };
- });
- // Included from: js/tinymce/classes/InsertContent.js
- /**
- * InsertContent.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Handles inserts of contents into the editor instance.
- *
- * @class tinymce.InsertContent
- * @private
- */
- define("tinymce/InsertContent", [
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/html/Serializer",
- "tinymce/caret/CaretWalker",
- "tinymce/caret/CaretPosition",
- "tinymce/dom/ElementUtils",
- "tinymce/dom/NodeType",
- "tinymce/InsertList"
- ], function(Env, Tools, Serializer, CaretWalker, CaretPosition, ElementUtils, NodeType, InsertList) {
- var isTableCell = NodeType.matchNodeNames('td th');
- var insertHtmlAtCaret = function(editor, value, details) {
- var parser, serializer, parentNode, rootNode, fragment, args;
- var marker, rng, node, node2, bookmarkHtml, merge;
- var textInlineElements = editor.schema.getTextInlineElements();
- var selection = editor.selection, dom = editor.dom;
- function trimOrPaddLeftRight(html) {
- var rng, container, offset;
- rng = selection.getRng(true);
- container = rng.startContainer;
- offset = rng.startOffset;
- function hasSiblingText(siblingName) {
- return container[siblingName] && container[siblingName].nodeType == 3;
- }
- if (container.nodeType == 3) {
- if (offset > 0) {
- html = html.replace(/^ /, ' ');
- } else if (!hasSiblingText('previousSibling')) {
- html = html.replace(/^ /, ' ');
- }
- if (offset < container.length) {
- html = html.replace(/ (<br>|)$/, ' ');
- } else if (!hasSiblingText('nextSibling')) {
- html = html.replace(/( | )(<br>|)$/, ' ');
- }
- }
- return html;
- }
- // Removes from a [b] c -> a c -> a c
- function trimNbspAfterDeleteAndPaddValue() {
- var rng, container, offset;
- rng = selection.getRng(true);
- container = rng.startContainer;
- offset = rng.startOffset;
- if (container.nodeType == 3 && rng.collapsed) {
- if (container.data[offset] === '\u00a0') {
- container.deleteData(offset, 1);
- if (!/[\u00a0| ]$/.test(value)) {
- value += ' ';
- }
- } else if (container.data[offset - 1] === '\u00a0') {
- container.deleteData(offset - 1, 1);
- if (!/[\u00a0| ]$/.test(value)) {
- value = ' ' + value;
- }
- }
- }
- }
- function reduceInlineTextElements() {
- if (merge) {
- var root = editor.getBody(), elementUtils = new ElementUtils(dom);
- Tools.each(dom.select('*[data-mce-fragment]'), function(node) {
- for (var testNode = node.parentNode; testNode && testNode != root; testNode = testNode.parentNode) {
- if (textInlineElements[node.nodeName.toLowerCase()] && elementUtils.compare(testNode, node)) {
- dom.remove(node, true);
- }
- }
- });
- }
- }
- function markFragmentElements(fragment) {
- var node = fragment;
- while ((node = node.walk())) {
- if (node.type === 1) {
- node.attr('data-mce-fragment', '1');
- }
- }
- }
- function umarkFragmentElements(elm) {
- Tools.each(elm.getElementsByTagName('*'), function(elm) {
- elm.removeAttribute('data-mce-fragment');
- });
- }
- function isPartOfFragment(node) {
- return !!node.getAttribute('data-mce-fragment');
- }
- function canHaveChildren(node) {
- return node && !editor.schema.getShortEndedElements()[node.nodeName];
- }
- function moveSelectionToMarker(marker) {
- var parentEditableFalseElm, parentBlock, nextRng;
- function getContentEditableFalseParent(node) {
- var root = editor.getBody();
- for (; node && node !== root; node = node.parentNode) {
- if (editor.dom.getContentEditable(node) === 'false') {
- return node;
- }
- }
- return null;
- }
- if (!marker) {
- return;
- }
- selection.scrollIntoView(marker);
- // If marker is in cE=false then move selection to that element instead
- parentEditableFalseElm = getContentEditableFalseParent(marker);
- if (parentEditableFalseElm) {
- dom.remove(marker);
- selection.select(parentEditableFalseElm);
- return;
- }
- // Move selection before marker and remove it
- rng = dom.createRng();
- // If previous sibling is a text node set the selection to the end of that node
- node = marker.previousSibling;
- if (node && node.nodeType == 3) {
- rng.setStart(node, node.nodeValue.length);
- // TODO: Why can't we normalize on IE
- if (!Env.ie) {
- node2 = marker.nextSibling;
- if (node2 && node2.nodeType == 3) {
- node.appendData(node2.data);
- node2.parentNode.removeChild(node2);
- }
- }
- } else {
- // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
- rng.setStartBefore(marker);
- rng.setEndBefore(marker);
- }
- function findNextCaretRng(rng) {
- var caretPos = CaretPosition.fromRangeStart(rng);
- var caretWalker = new CaretWalker(editor.getBody());
- caretPos = caretWalker.next(caretPos);
- if (caretPos) {
- return caretPos.toRange();
- }
- }
- // Remove the marker node and set the new range
- parentBlock = dom.getParent(marker, dom.isBlock);
- dom.remove(marker);
- if (parentBlock && dom.isEmpty(parentBlock)) {
- editor.$(parentBlock).empty();
- rng.setStart(parentBlock, 0);
- rng.setEnd(parentBlock, 0);
- if (!isTableCell(parentBlock) && !isPartOfFragment(parentBlock) && (nextRng = findNextCaretRng(rng))) {
- rng = nextRng;
- dom.remove(parentBlock);
- } else {
- dom.add(parentBlock, dom.create('br', {'data-mce-bogus': '1'}));
- }
- }
- selection.setRng(rng);
- }
- // Check for whitespace before/after value
- if (/^ | $/.test(value)) {
- value = trimOrPaddLeftRight(value);
- }
- // Setup parser and serializer
- parser = editor.parser;
- merge = details.merge;
- serializer = new Serializer({
- validate: editor.settings.validate
- }, editor.schema);
- bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">​</span>';
- // Run beforeSetContent handlers on the HTML to be inserted
- args = {content: value, format: 'html', selection: true};
- editor.fire('BeforeSetContent', args);
- value = args.content;
- // Add caret at end of contents if it's missing
- if (value.indexOf('{$caret}') == -1) {
- value += '{$caret}';
- }
- // Replace the caret marker with a span bookmark element
- value = value.replace(/\{\$caret\}/, bookmarkHtml);
- // If selection is at <body>|<p></p> then move it into <body><p>|</p>
- rng = selection.getRng();
- var caretElement = rng.startContainer || (rng.parentElement ? rng.parentElement() : null);
- var body = editor.getBody();
- if (caretElement === body && selection.isCollapsed()) {
- if (dom.isBlock(body.firstChild) && canHaveChildren(body.firstChild) && dom.isEmpty(body.firstChild)) {
- rng = dom.createRng();
- rng.setStart(body.firstChild, 0);
- rng.setEnd(body.firstChild, 0);
- selection.setRng(rng);
- }
- }
- // Insert node maker where we will insert the new HTML and get it's parent
- if (!selection.isCollapsed()) {
- // Fix for #2595 seems that delete removes one extra character on
- // WebKit for some odd reason if you double click select a word
- editor.selection.setRng(editor.selection.getRng());
- editor.getDoc().execCommand('Delete', false, null);
- trimNbspAfterDeleteAndPaddValue();
- }
- parentNode = selection.getNode();
- // Parse the fragment within the context of the parent node
- var parserArgs = {context: parentNode.nodeName.toLowerCase(), data: details.data};
- fragment = parser.parse(value, parserArgs);
- // Custom handling of lists
- if (details.paste === true && InsertList.isListFragment(fragment) && InsertList.isParentBlockLi(dom, parentNode)) {
- rng = InsertList.insertAtCaret(serializer, dom, editor.selection.getRng(true), fragment);
- editor.selection.setRng(rng);
- editor.fire('SetContent', args);
- return;
- }
- markFragmentElements(fragment);
- // Move the caret to a more suitable location
- node = fragment.lastChild;
- if (node.attr('id') == 'mce_marker') {
- marker = node;
- for (node = node.prev; node; node = node.walk(true)) {
- if (node.type == 3 || !dom.isBlock(node.name)) {
- if (editor.schema.isValidChild(node.parent.name, 'span')) {
- node.parent.insert(marker, node, node.name === 'br');
- }
- break;
- }
- }
- }
- editor._selectionOverrides.showBlockCaretContainer(parentNode);
- // If parser says valid we can insert the contents into that parent
- if (!parserArgs.invalid) {
- value = serializer.serialize(fragment);
- // Check if parent is empty or only has one BR element then set the innerHTML of that parent
- node = parentNode.firstChild;
- node2 = parentNode.lastChild;
- if (!node || (node === node2 && node.nodeName === 'BR')) {
- dom.setHTML(parentNode, value);
- } else {
- selection.setContent(value);
- }
- } else {
- // If the fragment was invalid within that context then we need
- // to parse and process the parent it's inserted into
- // Insert bookmark node and get the parent
- selection.setContent(bookmarkHtml);
- parentNode = selection.getNode();
- rootNode = editor.getBody();
- // Opera will return the document node when selection is in root
- if (parentNode.nodeType == 9) {
- parentNode = node = rootNode;
- } else {
- node = parentNode;
- }
- // Find the ancestor just before the root element
- while (node !== rootNode) {
- parentNode = node;
- node = node.parentNode;
- }
- // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
- value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
- value = serializer.serialize(
- parser.parse(
- // Need to replace by using a function since $ in the contents would otherwise be a problem
- value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
- return serializer.serialize(fragment);
- })
- )
- );
- // Set the inner/outer HTML depending on if we are in the root or not
- if (parentNode == rootNode) {
- dom.setHTML(rootNode, value);
- } else {
- dom.setOuterHTML(parentNode, value);
- }
- }
- reduceInlineTextElements();
- moveSelectionToMarker(dom.get('mce_marker'));
- umarkFragmentElements(editor.getBody());
- editor.fire('SetContent', args);
- editor.addVisual();
- };
- var processValue = function (value) {
- var details;
- if (typeof value !== 'string') {
- details = Tools.extend({
- paste: value.paste,
- data: {
- paste: value.paste
- }
- }, value);
- return {
- content: value.content,
- details: details
- };
- }
- return {
- content: value,
- details: {}
- };
- };
- var insertAtCaret = function (editor, value) {
- var result = processValue(value);
- insertHtmlAtCaret(editor, result.content, result.details);
- };
- return {
- insertAtCaret: insertAtCaret
- };
- });
- // Included from: js/tinymce/classes/EditorCommands.js
- /**
- * EditorCommands.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class enables you to add custom editor commands and it contains
- * overrides for native browser commands to address various bugs and issues.
- *
- * @class tinymce.EditorCommands
- */
- define("tinymce/EditorCommands", [
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/dom/RangeUtils",
- "tinymce/dom/TreeWalker",
- "tinymce/InsertContent"
- ], function(Env, Tools, RangeUtils, TreeWalker, InsertContent) {
- // Added for compression purposes
- var each = Tools.each, extend = Tools.extend;
- var map = Tools.map, inArray = Tools.inArray, explode = Tools.explode;
- var isOldIE = Env.ie && Env.ie < 11;
- var TRUE = true, FALSE = false;
- return function(editor) {
- var dom, selection, formatter,
- commands = {state: {}, exec: {}, value: {}},
- settings = editor.settings,
- bookmark;
- editor.on('PreInit', function() {
- dom = editor.dom;
- selection = editor.selection;
- settings = editor.settings;
- formatter = editor.formatter;
- });
- /**
- * Executes the specified command.
- *
- * @method execCommand
- * @param {String} command Command to execute.
- * @param {Boolean} ui Optional user interface state.
- * @param {Object} value Optional value for command.
- * @param {Object} args Optional extra arguments to the execCommand.
- * @return {Boolean} true/false if the command was found or not.
- */
- function execCommand(command, ui, value, args) {
- var func, customCommand, state = 0;
- if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(command) && (!args || !args.skip_focus)) {
- editor.focus();
- }
- args = editor.fire('BeforeExecCommand', {command: command, ui: ui, value: value});
- if (args.isDefaultPrevented()) {
- return false;
- }
- customCommand = command.toLowerCase();
- if ((func = commands.exec[customCommand])) {
- func(customCommand, ui, value);
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
- return true;
- }
- // Plugin commands
- each(editor.plugins, function(p) {
- if (p.execCommand && p.execCommand(command, ui, value)) {
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
- state = true;
- return false;
- }
- });
- if (state) {
- return state;
- }
- // Theme commands
- if (editor.theme && editor.theme.execCommand && editor.theme.execCommand(command, ui, value)) {
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
- return true;
- }
- // Browser commands
- try {
- state = editor.getDoc().execCommand(command, ui, value);
- } catch (ex) {
- // Ignore old IE errors
- }
- if (state) {
- editor.fire('ExecCommand', {command: command, ui: ui, value: value});
- return true;
- }
- return false;
- }
- /**
- * Queries the current state for a command for example if the current selection is "bold".
- *
- * @method queryCommandState
- * @param {String} command Command to check the state of.
- * @return {Boolean/Number} true/false if the selected contents is bold or not, -1 if it's not found.
- */
- function queryCommandState(command) {
- var func;
- // Is hidden then return undefined
- if (editor.quirks.isHidden()) {
- return;
- }
- command = command.toLowerCase();
- if ((func = commands.state[command])) {
- return func(command);
- }
- // Browser commands
- try {
- return editor.getDoc().queryCommandState(command);
- } catch (ex) {
- // Fails sometimes see bug: 1896577
- }
- return false;
- }
- /**
- * Queries the command value for example the current fontsize.
- *
- * @method queryCommandValue
- * @param {String} command Command to check the value of.
- * @return {Object} Command value of false if it's not found.
- */
- function queryCommandValue(command) {
- var func;
- // Is hidden then return undefined
- if (editor.quirks.isHidden()) {
- return;
- }
- command = command.toLowerCase();
- if ((func = commands.value[command])) {
- return func(command);
- }
- // Browser commands
- try {
- return editor.getDoc().queryCommandValue(command);
- } catch (ex) {
- // Fails sometimes see bug: 1896577
- }
- }
- /**
- * Adds commands to the command collection.
- *
- * @method addCommands
- * @param {Object} command_list Name/value collection with commands to add, the names can also be comma separated.
- * @param {String} type Optional type to add, defaults to exec. Can be value or state as well.
- */
- function addCommands(command_list, type) {
- type = type || 'exec';
- each(command_list, function(callback, command) {
- each(command.toLowerCase().split(','), function(command) {
- commands[type][command] = callback;
- });
- });
- }
- function addCommand(command, callback, scope) {
- command = command.toLowerCase();
- commands.exec[command] = function(command, ui, value, args) {
- return callback.call(scope || editor, ui, value, args);
- };
- }
- /**
- * Returns true/false if the command is supported or not.
- *
- * @method queryCommandSupported
- * @param {String} command Command that we check support for.
- * @return {Boolean} true/false if the command is supported or not.
- */
- function queryCommandSupported(command) {
- command = command.toLowerCase();
- if (commands.exec[command]) {
- return true;
- }
- // Browser commands
- try {
- return editor.getDoc().queryCommandSupported(command);
- } catch (ex) {
- // Fails sometimes see bug: 1896577
- }
- return false;
- }
- function addQueryStateHandler(command, callback, scope) {
- command = command.toLowerCase();
- commands.state[command] = function() {
- return callback.call(scope || editor);
- };
- }
- function addQueryValueHandler(command, callback, scope) {
- command = command.toLowerCase();
- commands.value[command] = function() {
- return callback.call(scope || editor);
- };
- }
- function hasCustomCommand(command) {
- command = command.toLowerCase();
- return !!commands.exec[command];
- }
- // Expose public methods
- extend(this, {
- execCommand: execCommand,
- queryCommandState: queryCommandState,
- queryCommandValue: queryCommandValue,
- queryCommandSupported: queryCommandSupported,
- addCommands: addCommands,
- addCommand: addCommand,
- addQueryStateHandler: addQueryStateHandler,
- addQueryValueHandler: addQueryValueHandler,
- hasCustomCommand: hasCustomCommand
- });
- // Private methods
- function execNativeCommand(command, ui, value) {
- if (ui === undefined) {
- ui = FALSE;
- }
- if (value === undefined) {
- value = null;
- }
- return editor.getDoc().execCommand(command, ui, value);
- }
- function isFormatMatch(name) {
- return formatter.match(name);
- }
- function toggleFormat(name, value) {
- formatter.toggle(name, value ? {value: value} : undefined);
- editor.nodeChanged();
- }
- function storeSelection(type) {
- bookmark = selection.getBookmark(type);
- }
- function restoreSelection() {
- selection.moveToBookmark(bookmark);
- }
- // Add execCommand overrides
- addCommands({
- // Ignore these, added for compatibility
- 'mceResetDesignMode,mceBeginUndoLevel': function() {},
- // Add undo manager logic
- 'mceEndUndoLevel,mceAddUndoLevel': function() {
- editor.undoManager.add();
- },
- 'Cut,Copy,Paste': function(command) {
- var doc = editor.getDoc(), failed;
- // Try executing the native command
- try {
- execNativeCommand(command);
- } catch (ex) {
- // Command failed
- failed = TRUE;
- }
- // Chrome reports the paste command as supported however older IE:s will return false for cut/paste
- if (command === 'paste' && !doc.queryCommandEnabled(command)) {
- failed = true;
- }
- // Present alert message about clipboard access not being available
- if (failed || !doc.queryCommandSupported(command)) {
- var msg = editor.translate(
- "Your browser doesn't support direct access to the clipboard. " +
- "Please use the Ctrl+X/C/V keyboard shortcuts instead."
- );
- if (Env.mac) {
- msg = msg.replace(/Ctrl\+/g, '\u2318+');
- }
- editor.notificationManager.open({text: msg, type: 'error'});
- }
- },
- // Override unlink command
- unlink: function() {
- if (selection.isCollapsed()) {
- var elm = selection.getNode();
- if (elm.tagName == 'A') {
- editor.dom.remove(elm, true);
- }
- return;
- }
- formatter.remove("link");
- },
- // Override justify commands to use the text formatter engine
- 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull,JustifyNone': function(command) {
- var align = command.substring(7);
- if (align == 'full') {
- align = 'justify';
- }
- // Remove all other alignments first
- each('left,center,right,justify'.split(','), function(name) {
- if (align != name) {
- formatter.remove('align' + name);
- }
- });
- if (align != 'none') {
- toggleFormat('align' + align);
- }
- },
- // Override list commands to fix WebKit bug
- 'InsertUnorderedList,InsertOrderedList': function(command) {
- var listElm, listParent;
- execNativeCommand(command);
- // WebKit produces lists within block elements so we need to split them
- // we will replace the native list creation logic to custom logic later on
- // TODO: Remove this when the list creation logic is removed
- listElm = dom.getParent(selection.getNode(), 'ol,ul');
- if (listElm) {
- listParent = listElm.parentNode;
- // If list is within a text block then split that block
- if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
- storeSelection();
- dom.split(listParent, listElm);
- restoreSelection();
- }
- }
- },
- // Override commands to use the text formatter engine
- 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
- toggleFormat(command);
- },
- // Override commands to use the text formatter engine
- 'ForeColor,HiliteColor,FontName': function(command, ui, value) {
- toggleFormat(command, value);
- },
- FontSize: function(command, ui, value) {
- var fontClasses, fontSizes;
- // Convert font size 1-7 to styles
- if (value >= 1 && value <= 7) {
- fontSizes = explode(settings.font_size_style_values);
- fontClasses = explode(settings.font_size_classes);
- if (fontClasses) {
- value = fontClasses[value - 1] || value;
- } else {
- value = fontSizes[value - 1] || value;
- }
- }
- toggleFormat(command, value);
- },
- RemoveFormat: function(command) {
- formatter.remove(command);
- },
- mceBlockQuote: function() {
- toggleFormat('blockquote');
- },
- FormatBlock: function(command, ui, value) {
- return toggleFormat(value || 'p');
- },
- mceCleanup: function() {
- var bookmark = selection.getBookmark();
- editor.setContent(editor.getContent({cleanup: TRUE}), {cleanup: TRUE});
- selection.moveToBookmark(bookmark);
- },
- mceRemoveNode: function(command, ui, value) {
- var node = value || selection.getNode();
- // Make sure that the body node isn't removed
- if (node != editor.getBody()) {
- storeSelection();
- editor.dom.remove(node, TRUE);
- restoreSelection();
- }
- },
- mceSelectNodeDepth: function(command, ui, value) {
- var counter = 0;
- dom.getParent(selection.getNode(), function(node) {
- if (node.nodeType == 1 && counter++ == value) {
- selection.select(node);
- return FALSE;
- }
- }, editor.getBody());
- },
- mceSelectNode: function(command, ui, value) {
- selection.select(value);
- },
- mceInsertContent: function(command, ui, value) {
- InsertContent.insertAtCaret(editor, value);
- },
- mceInsertRawHTML: function(command, ui, value) {
- selection.setContent('tiny_mce_marker');
- editor.setContent(
- editor.getContent().replace(/tiny_mce_marker/g, function() {
- return value;
- })
- );
- },
- mceToggleFormat: function(command, ui, value) {
- toggleFormat(value);
- },
- mceSetContent: function(command, ui, value) {
- editor.setContent(value);
- },
- 'Indent,Outdent': function(command) {
- var intentValue, indentUnit, value;
- // Setup indent level
- intentValue = settings.indentation;
- indentUnit = /[a-z%]+$/i.exec(intentValue);
- intentValue = parseInt(intentValue, 10);
- if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
- // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
- if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
- formatter.apply('div');
- }
- each(selection.getSelectedBlocks(), function(element) {
- if (dom.getContentEditable(element) === "false") {
- return;
- }
- if (element.nodeName != "LI") {
- var indentStyleName = editor.getParam('indent_use_margin', false) ? 'margin' : 'padding';
- indentStyleName += dom.getStyle(element, 'direction', true) == 'rtl' ? 'Right' : 'Left';
- if (command == 'outdent') {
- value = Math.max(0, parseInt(element.style[indentStyleName] || 0, 10) - intentValue);
- dom.setStyle(element, indentStyleName, value ? value + indentUnit : '');
- } else {
- value = (parseInt(element.style[indentStyleName] || 0, 10) + intentValue) + indentUnit;
- dom.setStyle(element, indentStyleName, value);
- }
- }
- });
- } else {
- execNativeCommand(command);
- }
- },
- mceRepaint: function() {
- },
- InsertHorizontalRule: function() {
- editor.execCommand('mceInsertContent', false, '<hr />');
- },
- mceToggleVisualAid: function() {
- editor.hasVisual = !editor.hasVisual;
- editor.addVisual();
- },
- mceReplaceContent: function(command, ui, value) {
- editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format: 'text'})));
- },
- mceInsertLink: function(command, ui, value) {
- var anchor;
- if (typeof value == 'string') {
- value = {href: value};
- }
- anchor = dom.getParent(selection.getNode(), 'a');
- // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
- value.href = value.href.replace(' ', '%20');
- // Remove existing links if there could be child links or that the href isn't specified
- if (!anchor || !value.href) {
- formatter.remove('link');
- }
- // Apply new link to selection
- if (value.href) {
- formatter.apply('link', value, anchor);
- }
- },
- selectAll: function() {
- var root = dom.getRoot(), rng;
- if (selection.getRng().setStart) {
- rng = dom.createRng();
- rng.setStart(root, 0);
- rng.setEnd(root, root.childNodes.length);
- selection.setRng(rng);
- } else {
- // IE will render it's own root level block elements and sometimes
- // even put font elements in them when the user starts typing. So we need to
- // move the selection to a more suitable element from this:
- // <body>|<p></p></body> to this: <body><p>|</p></body>
- rng = selection.getRng();
- if (!rng.item) {
- rng.moveToElementText(root);
- rng.select();
- }
- }
- },
- "delete": function() {
- execNativeCommand("Delete");
- // Check if body is empty after the delete call if so then set the contents
- // to an empty string and move the caret to any block produced by that operation
- // this fixes the issue with root blocks not being properly produced after a delete call on IE
- var body = editor.getBody();
- if (dom.isEmpty(body)) {
- editor.setContent('');
- if (body.firstChild && dom.isBlock(body.firstChild)) {
- editor.selection.setCursorLocation(body.firstChild, 0);
- } else {
- editor.selection.setCursorLocation(body, 0);
- }
- }
- },
- mceNewDocument: function() {
- editor.setContent('');
- },
- InsertLineBreak: function(command, ui, value) {
- // We load the current event in from EnterKey.js when appropriate to heed
- // certain event-specific variations such as ctrl-enter in a list
- var evt = value;
- var brElm, extraBr, marker;
- var rng = selection.getRng(true);
- new RangeUtils(dom).normalize(rng);
- var offset = rng.startOffset;
- var container = rng.startContainer;
- // Resolve node index
- if (container.nodeType == 1 && container.hasChildNodes()) {
- var isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
- container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
- if (isAfterLastNodeInContainer && container.nodeType == 3) {
- offset = container.nodeValue.length;
- } else {
- offset = 0;
- }
- }
- var parentBlock = dom.getParent(container, dom.isBlock);
- var parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
- var containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
- var containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
- // Enter inside block contained within a LI then split or insert before/after LI
- var isControlKey = evt && evt.ctrlKey;
- if (containerBlockName == 'LI' && !isControlKey) {
- parentBlock = containerBlock;
- parentBlockName = containerBlockName;
- }
- // Walks the parent block to the right and look for BR elements
- function hasRightSideContent() {
- var walker = new TreeWalker(container, parentBlock), node;
- var nonEmptyElementsMap = editor.schema.getNonEmptyElements();
- while ((node = walker.next())) {
- if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
- return true;
- }
- }
- }
- if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
- // Insert extra BR element at the end block elements
- if (!isOldIE && !hasRightSideContent()) {
- brElm = dom.create('br');
- rng.insertNode(brElm);
- rng.setStartAfter(brElm);
- rng.setEndAfter(brElm);
- extraBr = true;
- }
- }
- brElm = dom.create('br');
- rng.insertNode(brElm);
- // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
- var documentMode = dom.doc.documentMode;
- if (isOldIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
- brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
- }
- // Insert temp marker and scroll to that
- marker = dom.create('span', {}, ' ');
- brElm.parentNode.insertBefore(marker, brElm);
- selection.scrollIntoView(marker);
- dom.remove(marker);
- if (!extraBr) {
- rng.setStartAfter(brElm);
- rng.setEndAfter(brElm);
- } else {
- rng.setStartBefore(brElm);
- rng.setEndBefore(brElm);
- }
- selection.setRng(rng);
- editor.undoManager.add();
- return TRUE;
- }
- });
- // Add queryCommandState overrides
- addCommands({
- // Override justify commands
- 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull': function(command) {
- var name = 'align' + command.substring(7);
- var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
- var matches = map(nodes, function(node) {
- return !!formatter.matchNode(node, name);
- });
- return inArray(matches, TRUE) !== -1;
- },
- 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': function(command) {
- return isFormatMatch(command);
- },
- mceBlockQuote: function() {
- return isFormatMatch('blockquote');
- },
- Outdent: function() {
- var node;
- if (settings.inline_styles) {
- if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
- return TRUE;
- }
- if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft, 10) > 0) {
- return TRUE;
- }
- }
- return (
- queryCommandState('InsertUnorderedList') ||
- queryCommandState('InsertOrderedList') ||
- (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'))
- );
- },
- 'InsertUnorderedList,InsertOrderedList': function(command) {
- var list = dom.getParent(selection.getNode(), 'ul,ol');
- return list &&
- (
- command === 'insertunorderedlist' && list.tagName === 'UL' ||
- command === 'insertorderedlist' && list.tagName === 'OL'
- );
- }
- }, 'state');
- // Add queryCommandValue overrides
- addCommands({
- 'FontSize,FontName': function(command) {
- var value = 0, parent;
- if ((parent = dom.getParent(selection.getNode(), 'span'))) {
- if (command == 'fontsize') {
- value = parent.style.fontSize;
- } else {
- value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
- }
- }
- return value;
- }
- }, 'value');
- // Add undo manager logic
- addCommands({
- Undo: function() {
- editor.undoManager.undo();
- },
- Redo: function() {
- editor.undoManager.redo();
- }
- });
- };
- });
- // Included from: js/tinymce/classes/util/URI.js
- /**
- * URI.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles parsing, modification and serialization of URI/URL strings.
- * @class tinymce.util.URI
- */
- define("tinymce/util/URI", [
- "tinymce/util/Tools"
- ], function(Tools) {
- var each = Tools.each, trim = Tools.trim;
- var queryParts = "source protocol authority userInfo user password host port relative path directory file query anchor".split(' ');
- var DEFAULT_PORTS = {
- 'ftp': 21,
- 'http': 80,
- 'https': 443,
- 'mailto': 25
- };
- /**
- * Constructs a new URI instance.
- *
- * @constructor
- * @method URI
- * @param {String} url URI string to parse.
- * @param {Object} settings Optional settings object.
- */
- function URI(url, settings) {
- var self = this, baseUri, base_url;
- url = trim(url);
- settings = self.settings = settings || {};
- baseUri = settings.base_uri;
- // Strange app protocol that isn't http/https or local anchor
- // For example: mailto,skype,tel etc.
- if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
- self.source = url;
- return;
- }
- var isProtocolRelative = url.indexOf('//') === 0;
- // Absolute path with no host, fake host and protocol
- if (url.indexOf('/') === 0 && !isProtocolRelative) {
- url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
- }
- // Relative path http:// or protocol relative //path
- if (!/^[\w\-]*:?\/\//.test(url)) {
- base_url = settings.base_uri ? settings.base_uri.path : new URI(location.href).directory;
- if (settings.base_uri.protocol === "") {
- url = '//mce_host' + self.toAbsPath(base_url, url);
- } else {
- url = /([^#?]*)([#?]?.*)/.exec(url);
- url = ((baseUri && baseUri.protocol) || 'http') + '://mce_host' + self.toAbsPath(base_url, url[1]) + url[2];
- }
- }
- // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
- url = url.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
- /*jshint maxlen: 255 */
- /*eslint max-len: 0 */
- url = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
- each(queryParts, function(v, i) {
- var part = url[i];
- // Zope 3 workaround, they use @@something
- if (part) {
- part = part.replace(/\(mce_at\)/g, '@@');
- }
- self[v] = part;
- });
- if (baseUri) {
- if (!self.protocol) {
- self.protocol = baseUri.protocol;
- }
- if (!self.userInfo) {
- self.userInfo = baseUri.userInfo;
- }
- if (!self.port && self.host === 'mce_host') {
- self.port = baseUri.port;
- }
- if (!self.host || self.host === 'mce_host') {
- self.host = baseUri.host;
- }
- self.source = '';
- }
- if (isProtocolRelative) {
- self.protocol = '';
- }
- //t.path = t.path || '/';
- }
- URI.prototype = {
- /**
- * Sets the internal path part of the URI.
- *
- * @method setPath
- * @param {string} path Path string to set.
- */
- setPath: function(path) {
- var self = this;
- path = /^(.*?)\/?(\w+)?$/.exec(path);
- // Update path parts
- self.path = path[0];
- self.directory = path[1];
- self.file = path[2];
- // Rebuild source
- self.source = '';
- self.getURI();
- },
- /**
- * Converts the specified URI into a relative URI based on the current URI instance location.
- *
- * @method toRelative
- * @param {String} uri URI to convert into a relative path/URI.
- * @return {String} Relative URI from the point specified in the current URI instance.
- * @example
- * // Converts an absolute URL to an relative URL url will be somedir/somefile.htm
- * var url = new tinymce.util.URI('http://www.site.com/dir/').toRelative('http://www.site.com/dir/somedir/somefile.htm');
- */
- toRelative: function(uri) {
- var self = this, output;
- if (uri === "./") {
- return uri;
- }
- uri = new URI(uri, {base_uri: self});
- // Not on same domain/port or protocol
- if ((uri.host != 'mce_host' && self.host != uri.host && uri.host) || self.port != uri.port ||
- (self.protocol != uri.protocol && uri.protocol !== "")) {
- return uri.getURI();
- }
- var tu = self.getURI(), uu = uri.getURI();
- // Allow usage of the base_uri when relative_urls = true
- if (tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu)) {
- return tu;
- }
- output = self.toRelPath(self.path, uri.path);
- // Add query
- if (uri.query) {
- output += '?' + uri.query;
- }
- // Add anchor
- if (uri.anchor) {
- output += '#' + uri.anchor;
- }
- return output;
- },
- /**
- * Converts the specified URI into a absolute URI based on the current URI instance location.
- *
- * @method toAbsolute
- * @param {String} uri URI to convert into a relative path/URI.
- * @param {Boolean} noHost No host and protocol prefix.
- * @return {String} Absolute URI from the point specified in the current URI instance.
- * @example
- * // Converts an relative URL to an absolute URL url will be http://www.site.com/dir/somedir/somefile.htm
- * var url = new tinymce.util.URI('http://www.site.com/dir/').toAbsolute('somedir/somefile.htm');
- */
- toAbsolute: function(uri, noHost) {
- uri = new URI(uri, {base_uri: this});
- return uri.getURI(noHost && this.isSameOrigin(uri));
- },
- /**
- * Determine whether the given URI has the same origin as this URI. Based on RFC-6454.
- * Supports default ports for protocols listed in DEFAULT_PORTS. Unsupported protocols will fail safe: they
- * won't match, if the port specifications differ.
- *
- * @method isSameOrigin
- * @param {tinymce.util.URI} uri Uri instance to compare.
- * @returns {Boolean} True if the origins are the same.
- */
- isSameOrigin: function(uri) {
- if (this.host == uri.host && this.protocol == uri.protocol) {
- if (this.port == uri.port) {
- return true;
- }
- var defaultPort = DEFAULT_PORTS[this.protocol];
- if (defaultPort && ((this.port || defaultPort) == (uri.port || defaultPort))) {
- return true;
- }
- }
- return false;
- },
- /**
- * Converts a absolute path into a relative path.
- *
- * @method toRelPath
- * @param {String} base Base point to convert the path from.
- * @param {String} path Absolute path to convert into a relative path.
- */
- toRelPath: function(base, path) {
- var items, breakPoint = 0, out = '', i, l;
- // Split the paths
- base = base.substring(0, base.lastIndexOf('/'));
- base = base.split('/');
- items = path.split('/');
- if (base.length >= items.length) {
- for (i = 0, l = base.length; i < l; i++) {
- if (i >= items.length || base[i] != items[i]) {
- breakPoint = i + 1;
- break;
- }
- }
- }
- if (base.length < items.length) {
- for (i = 0, l = items.length; i < l; i++) {
- if (i >= base.length || base[i] != items[i]) {
- breakPoint = i + 1;
- break;
- }
- }
- }
- if (breakPoint === 1) {
- return path;
- }
- for (i = 0, l = base.length - (breakPoint - 1); i < l; i++) {
- out += "../";
- }
- for (i = breakPoint - 1, l = items.length; i < l; i++) {
- if (i != breakPoint - 1) {
- out += "/" + items[i];
- } else {
- out += items[i];
- }
- }
- return out;
- },
- /**
- * Converts a relative path into a absolute path.
- *
- * @method toAbsPath
- * @param {String} base Base point to convert the path from.
- * @param {String} path Relative path to convert into an absolute path.
- */
- toAbsPath: function(base, path) {
- var i, nb = 0, o = [], tr, outPath;
- // Split paths
- tr = /\/$/.test(path) ? '/' : '';
- base = base.split('/');
- path = path.split('/');
- // Remove empty chunks
- each(base, function(k) {
- if (k) {
- o.push(k);
- }
- });
- base = o;
- // Merge relURLParts chunks
- for (i = path.length - 1, o = []; i >= 0; i--) {
- // Ignore empty or .
- if (path[i].length === 0 || path[i] === ".") {
- continue;
- }
- // Is parent
- if (path[i] === '..') {
- nb++;
- continue;
- }
- // Move up
- if (nb > 0) {
- nb--;
- continue;
- }
- o.push(path[i]);
- }
- i = base.length - nb;
- // If /a/b/c or /
- if (i <= 0) {
- outPath = o.reverse().join('/');
- } else {
- outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
- }
- // Add front / if it's needed
- if (outPath.indexOf('/') !== 0) {
- outPath = '/' + outPath;
- }
- // Add traling / if it's needed
- if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
- outPath += tr;
- }
- return outPath;
- },
- /**
- * Returns the full URI of the internal structure.
- *
- * @method getURI
- * @param {Boolean} noProtoHost Optional no host and protocol part. Defaults to false.
- */
- getURI: function(noProtoHost) {
- var s, self = this;
- // Rebuild source
- if (!self.source || noProtoHost) {
- s = '';
- if (!noProtoHost) {
- if (self.protocol) {
- s += self.protocol + '://';
- } else {
- s += '//';
- }
- if (self.userInfo) {
- s += self.userInfo + '@';
- }
- if (self.host) {
- s += self.host;
- }
- if (self.port) {
- s += ':' + self.port;
- }
- }
- if (self.path) {
- s += self.path;
- }
- if (self.query) {
- s += '?' + self.query;
- }
- if (self.anchor) {
- s += '#' + self.anchor;
- }
- self.source = s;
- }
- return self.source;
- }
- };
- URI.parseDataUri = function(uri) {
- var type, matches;
- uri = decodeURIComponent(uri).split(',');
- matches = /data:([^;]+)/.exec(uri[0]);
- if (matches) {
- type = matches[1];
- }
- return {
- type: type,
- data: uri[1]
- };
- };
- URI.getDocumentBaseUrl = function(loc) {
- var baseUrl;
- // Pass applewebdata:// and other non web protocols though
- if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') {
- baseUrl = loc.href;
- } else {
- baseUrl = loc.protocol + '//' + loc.host + loc.pathname;
- }
- if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) {
- baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
- if (!/[\/\\]$/.test(baseUrl)) {
- baseUrl += '/';
- }
- }
- return baseUrl;
- };
- return URI;
- });
- // Included from: js/tinymce/classes/util/Class.js
- /**
- * Class.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This utilitiy class is used for easier inheritance.
- *
- * Features:
- * * Exposed super functions: this._super();
- * * Mixins
- * * Dummy functions
- * * Property functions: var value = object.value(); and object.value(newValue);
- * * Static functions
- * * Defaults settings
- */
- define("tinymce/util/Class", [
- "tinymce/util/Tools"
- ], function(Tools) {
- var each = Tools.each, extend = Tools.extend;
- var extendClass, initializing;
- function Class() {
- }
- // Provides classical inheritance, based on code made by John Resig
- Class.extend = extendClass = function(prop) {
- var self = this, _super = self.prototype, prototype, name, member;
- // The dummy class constructor
- function Class() {
- var i, mixins, mixin, self = this;
- // All construction is actually done in the init method
- if (!initializing) {
- // Run class constuctor
- if (self.init) {
- self.init.apply(self, arguments);
- }
- // Run mixin constructors
- mixins = self.Mixins;
- if (mixins) {
- i = mixins.length;
- while (i--) {
- mixin = mixins[i];
- if (mixin.init) {
- mixin.init.apply(self, arguments);
- }
- }
- }
- }
- }
- // Dummy function, needs to be extended in order to provide functionality
- function dummy() {
- return this;
- }
- // Creates a overloaded method for the class
- // this enables you to use this._super(); to call the super function
- function createMethod(name, fn) {
- return function() {
- var self = this, tmp = self._super, ret;
- self._super = _super[name];
- ret = fn.apply(self, arguments);
- self._super = tmp;
- return ret;
- };
- }
- // Instantiate a base class (but only create the instance,
- // don't run the init constructor)
- initializing = true;
- /*eslint new-cap:0 */
- prototype = new self();
- initializing = false;
- // Add mixins
- if (prop.Mixins) {
- each(prop.Mixins, function(mixin) {
- for (var name in mixin) {
- if (name !== "init") {
- prop[name] = mixin[name];
- }
- }
- });
- if (_super.Mixins) {
- prop.Mixins = _super.Mixins.concat(prop.Mixins);
- }
- }
- // Generate dummy methods
- if (prop.Methods) {
- each(prop.Methods.split(','), function(name) {
- prop[name] = dummy;
- });
- }
- // Generate property methods
- if (prop.Properties) {
- each(prop.Properties.split(','), function(name) {
- var fieldName = '_' + name;
- prop[name] = function(value) {
- var self = this, undef;
- // Set value
- if (value !== undef) {
- self[fieldName] = value;
- return self;
- }
- // Get value
- return self[fieldName];
- };
- });
- }
- // Static functions
- if (prop.Statics) {
- each(prop.Statics, function(func, name) {
- Class[name] = func;
- });
- }
- // Default settings
- if (prop.Defaults && _super.Defaults) {
- prop.Defaults = extend({}, _super.Defaults, prop.Defaults);
- }
- // Copy the properties over onto the new prototype
- for (name in prop) {
- member = prop[name];
- if (typeof member == "function" && _super[name]) {
- prototype[name] = createMethod(name, member);
- } else {
- prototype[name] = member;
- }
- }
- // Populate our constructed prototype object
- Class.prototype = prototype;
- // Enforce the constructor to be what we expect
- Class.constructor = Class;
- // And make this class extendible
- Class.extend = extendClass;
- return Class;
- };
- return Class;
- });
- // Included from: js/tinymce/classes/util/EventDispatcher.js
- /**
- * EventDispatcher.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class lets you add/remove and fire events by name on the specified scope. This makes
- * it easy to add event listener logic to any class.
- *
- * @class tinymce.util.EventDispatcher
- * @example
- * var eventDispatcher = new EventDispatcher();
- *
- * eventDispatcher.on('click', function() {console.log('data');});
- * eventDispatcher.fire('click', {data: 123});
- */
- define("tinymce/util/EventDispatcher", [
- "tinymce/util/Tools"
- ], function(Tools) {
- var nativeEvents = Tools.makeMap(
- "focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange " +
- "mouseout mouseenter mouseleave wheel keydown keypress keyup input contextmenu dragstart dragend dragover " +
- "draggesture dragdrop drop drag submit " +
- "compositionstart compositionend compositionupdate touchstart touchmove touchend",
- ' '
- );
- function Dispatcher(settings) {
- var self = this, scope, bindings = {}, toggleEvent;
- function returnFalse() {
- return false;
- }
- function returnTrue() {
- return true;
- }
- settings = settings || {};
- scope = settings.scope || self;
- toggleEvent = settings.toggleEvent || returnFalse;
- /**
- * Fires the specified event by name.
- *
- * @method fire
- * @param {String} name Name of the event to fire.
- * @param {Object?} args Event arguments.
- * @return {Object} Event args instance passed in.
- * @example
- * instance.fire('event', {...});
- */
- function fire(name, args) {
- var handlers, i, l, callback;
- name = name.toLowerCase();
- args = args || {};
- args.type = name;
- // Setup target is there isn't one
- if (!args.target) {
- args.target = scope;
- }
- // Add event delegation methods if they are missing
- if (!args.preventDefault) {
- // Add preventDefault method
- args.preventDefault = function() {
- args.isDefaultPrevented = returnTrue;
- };
- // Add stopPropagation
- args.stopPropagation = function() {
- args.isPropagationStopped = returnTrue;
- };
- // Add stopImmediatePropagation
- args.stopImmediatePropagation = function() {
- args.isImmediatePropagationStopped = returnTrue;
- };
- // Add event delegation states
- args.isDefaultPrevented = returnFalse;
- args.isPropagationStopped = returnFalse;
- args.isImmediatePropagationStopped = returnFalse;
- }
- if (settings.beforeFire) {
- settings.beforeFire(args);
- }
- handlers = bindings[name];
- if (handlers) {
- for (i = 0, l = handlers.length; i < l; i++) {
- callback = handlers[i];
- // Unbind handlers marked with "once"
- if (callback.once) {
- off(name, callback.func);
- }
- // Stop immediate propagation if needed
- if (args.isImmediatePropagationStopped()) {
- args.stopPropagation();
- return args;
- }
- // If callback returns false then prevent default and stop all propagation
- if (callback.func.call(scope, args) === false) {
- args.preventDefault();
- return args;
- }
- }
- }
- return args;
- }
- /**
- * Binds an event listener to a specific event by name.
- *
- * @method on
- * @param {String} name Event name or space separated list of events to bind.
- * @param {callback} callback Callback to be executed when the event occurs.
- * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
- * @return {Object} Current class instance.
- * @example
- * instance.on('event', function(e) {
- * // Callback logic
- * });
- */
- function on(name, callback, prepend, extra) {
- var handlers, names, i;
- if (callback === false) {
- callback = returnFalse;
- }
- if (callback) {
- callback = {
- func: callback
- };
- if (extra) {
- Tools.extend(callback, extra);
- }
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- handlers = bindings[name];
- if (!handlers) {
- handlers = bindings[name] = [];
- toggleEvent(name, true);
- }
- if (prepend) {
- handlers.unshift(callback);
- } else {
- handlers.push(callback);
- }
- }
- }
- return self;
- }
- /**
- * Unbinds an event listener to a specific event by name.
- *
- * @method off
- * @param {String?} name Name of the event to unbind.
- * @param {callback?} callback Callback to unbind.
- * @return {Object} Current class instance.
- * @example
- * // Unbind specific callback
- * instance.off('event', handler);
- *
- * // Unbind all listeners by name
- * instance.off('event');
- *
- * // Unbind all events
- * instance.off();
- */
- function off(name, callback) {
- var i, handlers, bindingName, names, hi;
- if (name) {
- names = name.toLowerCase().split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- handlers = bindings[name];
- // Unbind all handlers
- if (!name) {
- for (bindingName in bindings) {
- toggleEvent(bindingName, false);
- delete bindings[bindingName];
- }
- return self;
- }
- if (handlers) {
- // Unbind all by name
- if (!callback) {
- handlers.length = 0;
- } else {
- // Unbind specific ones
- hi = handlers.length;
- while (hi--) {
- if (handlers[hi].func === callback) {
- handlers = handlers.slice(0, hi).concat(handlers.slice(hi + 1));
- bindings[name] = handlers;
- }
- }
- }
- if (!handlers.length) {
- toggleEvent(name, false);
- delete bindings[name];
- }
- }
- }
- } else {
- for (name in bindings) {
- toggleEvent(name, false);
- }
- bindings = {};
- }
- return self;
- }
- /**
- * Binds an event listener to a specific event by name
- * and automatically unbind the event once the callback fires.
- *
- * @method once
- * @param {String} name Event name or space separated list of events to bind.
- * @param {callback} callback Callback to be executed when the event occurs.
- * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
- * @return {Object} Current class instance.
- * @example
- * instance.once('event', function(e) {
- * // Callback logic
- * });
- */
- function once(name, callback, prepend) {
- return on(name, callback, prepend, {once: true});
- }
- /**
- * Returns true/false if the dispatcher has a event of the specified name.
- *
- * @method has
- * @param {String} name Name of the event to check for.
- * @return {Boolean} true/false if the event exists or not.
- */
- function has(name) {
- name = name.toLowerCase();
- return !(!bindings[name] || bindings[name].length === 0);
- }
- // Expose
- self.fire = fire;
- self.on = on;
- self.off = off;
- self.once = once;
- self.has = has;
- }
- /**
- * Returns true/false if the specified event name is a native browser event or not.
- *
- * @method isNative
- * @param {String} name Name to check if it's native.
- * @return {Boolean} true/false if the event is native or not.
- * @static
- */
- Dispatcher.isNative = function(name) {
- return !!nativeEvents[name.toLowerCase()];
- };
- return Dispatcher;
- });
- // Included from: js/tinymce/classes/data/Binding.js
- /**
- * Binding.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class gets dynamically extended to provide a binding between two models. This makes it possible to
- * sync the state of two properties in two models by a layer of abstraction.
- *
- * @private
- * @class tinymce.data.Binding
- */
- define("tinymce/data/Binding", [], function() {
- /**
- * Constructs a new bidning.
- *
- * @constructor
- * @method Binding
- * @param {Object} settings Settings to the binding.
- */
- function Binding(settings) {
- this.create = settings.create;
- }
- /**
- * Creates a binding for a property on a model.
- *
- * @method create
- * @param {tinymce.data.ObservableObject} model Model to create binding to.
- * @param {String} name Name of property to bind.
- * @return {tinymce.data.Binding} Binding instance.
- */
- Binding.create = function(model, name) {
- return new Binding({
- create: function(otherModel, otherName) {
- var bindings;
- function fromSelfToOther(e) {
- otherModel.set(otherName, e.value);
- }
- function fromOtherToSelf(e) {
- model.set(name, e.value);
- }
- otherModel.on('change:' + otherName, fromOtherToSelf);
- model.on('change:' + name, fromSelfToOther);
- // Keep track of the bindings
- bindings = otherModel._bindings;
- if (!bindings) {
- bindings = otherModel._bindings = [];
- otherModel.on('destroy', function() {
- var i = bindings.length;
- while (i--) {
- bindings[i]();
- }
- });
- }
- bindings.push(function() {
- model.off('change:' + name, fromSelfToOther);
- });
- return model.get(name);
- }
- });
- };
- return Binding;
- });
- // Included from: js/tinymce/classes/util/Observable.js
- /**
- * Observable.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This mixin will add event binding logic to classes.
- *
- * @mixin tinymce.util.Observable
- */
- define("tinymce/util/Observable", [
- "tinymce/util/EventDispatcher"
- ], function(EventDispatcher) {
- function getEventDispatcher(obj) {
- if (!obj._eventDispatcher) {
- obj._eventDispatcher = new EventDispatcher({
- scope: obj,
- toggleEvent: function(name, state) {
- if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
- obj.toggleNativeEvent(name, state);
- }
- }
- });
- }
- return obj._eventDispatcher;
- }
- return {
- /**
- * Fires the specified event by name.
- *
- * @method fire
- * @param {String} name Name of the event to fire.
- * @param {Object?} args Event arguments.
- * @param {Boolean?} bubble True/false if the event is to be bubbled.
- * @return {Object} Event args instance passed in.
- * @example
- * instance.fire('event', {...});
- */
- fire: function(name, args, bubble) {
- var self = this;
- // Prevent all events except the remove event after the instance has been removed
- if (self.removed && name !== "remove") {
- return args;
- }
- args = getEventDispatcher(self).fire(name, args, bubble);
- // Bubble event up to parents
- if (bubble !== false && self.parent) {
- var parent = self.parent();
- while (parent && !args.isPropagationStopped()) {
- parent.fire(name, args, false);
- parent = parent.parent();
- }
- }
- return args;
- },
- /**
- * Binds an event listener to a specific event by name.
- *
- * @method on
- * @param {String} name Event name or space separated list of events to bind.
- * @param {callback} callback Callback to be executed when the event occurs.
- * @param {Boolean} first Optional flag if the event should be prepended. Use this with care.
- * @return {Object} Current class instance.
- * @example
- * instance.on('event', function(e) {
- * // Callback logic
- * });
- */
- on: function(name, callback, prepend) {
- return getEventDispatcher(this).on(name, callback, prepend);
- },
- /**
- * Unbinds an event listener to a specific event by name.
- *
- * @method off
- * @param {String?} name Name of the event to unbind.
- * @param {callback?} callback Callback to unbind.
- * @return {Object} Current class instance.
- * @example
- * // Unbind specific callback
- * instance.off('event', handler);
- *
- * // Unbind all listeners by name
- * instance.off('event');
- *
- * // Unbind all events
- * instance.off();
- */
- off: function(name, callback) {
- return getEventDispatcher(this).off(name, callback);
- },
- /**
- * Bind the event callback and once it fires the callback is removed.
- *
- * @method once
- * @param {String} name Name of the event to bind.
- * @param {callback} callback Callback to bind only once.
- * @return {Object} Current class instance.
- */
- once: function(name, callback) {
- return getEventDispatcher(this).once(name, callback);
- },
- /**
- * Returns true/false if the object has a event of the specified name.
- *
- * @method hasEventListeners
- * @param {String} name Name of the event to check for.
- * @return {Boolean} true/false if the event exists or not.
- */
- hasEventListeners: function(name) {
- return getEventDispatcher(this).has(name);
- }
- };
- });
- // Included from: js/tinymce/classes/data/ObservableObject.js
- /**
- * ObservableObject.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is a object that is observable when properties changes a change event gets emitted.
- *
- * @private
- * @class tinymce.data.ObservableObject
- */
- define("tinymce/data/ObservableObject", [
- "tinymce/data/Binding",
- "tinymce/util/Observable",
- "tinymce/util/Class",
- "tinymce/util/Tools"
- ], function(Binding, Observable, Class, Tools) {
- function isNode(node) {
- return node.nodeType > 0;
- }
- // Todo: Maybe this should be shallow compare since it might be huge object references
- function isEqual(a, b) {
- var k, checked;
- // Strict equals
- if (a === b) {
- return true;
- }
- // Compare null
- if (a === null || b === null) {
- return a === b;
- }
- // Compare number, boolean, string, undefined
- if (typeof a !== "object" || typeof b !== "object") {
- return a === b;
- }
- // Compare arrays
- if (Tools.isArray(b)) {
- if (a.length !== b.length) {
- return false;
- }
- k = a.length;
- while (k--) {
- if (!isEqual(a[k], b[k])) {
- return false;
- }
- }
- }
- // Shallow compare nodes
- if (isNode(a) || isNode(b)) {
- return a === b;
- }
- // Compare objects
- checked = {};
- for (k in b) {
- if (!isEqual(a[k], b[k])) {
- return false;
- }
- checked[k] = true;
- }
- for (k in a) {
- if (!checked[k] && !isEqual(a[k], b[k])) {
- return false;
- }
- }
- return true;
- }
- return Class.extend({
- Mixins: [Observable],
- /**
- * Constructs a new observable object instance.
- *
- * @constructor
- * @param {Object} data Initial data for the object.
- */
- init: function(data) {
- var name, value;
- data = data || {};
- for (name in data) {
- value = data[name];
- if (value instanceof Binding) {
- data[name] = value.create(this, name);
- }
- }
- this.data = data;
- },
- /**
- * Sets a property on the value this will call
- * observers if the value is a change from the current value.
- *
- * @method set
- * @param {String/object} name Name of the property to set or a object of items to set.
- * @param {Object} value Value to set for the property.
- * @return {tinymce.data.ObservableObject} Observable object instance.
- */
- set: function(name, value) {
- var key, args, oldValue = this.data[name];
- if (value instanceof Binding) {
- value = value.create(this, name);
- }
- if (typeof name === "object") {
- for (key in name) {
- this.set(key, name[key]);
- }
- return this;
- }
- if (!isEqual(oldValue, value)) {
- this.data[name] = value;
- args = {
- target: this,
- name: name,
- value: value,
- oldValue: oldValue
- };
- this.fire('change:' + name, args);
- this.fire('change', args);
- }
- return this;
- },
- /**
- * Gets a property by name.
- *
- * @method get
- * @param {String} name Name of the property to get.
- * @return {Object} Object value of propery.
- */
- get: function(name) {
- return this.data[name];
- },
- /**
- * Returns true/false if the specified property exists.
- *
- * @method has
- * @param {String} name Name of the property to check for.
- * @return {Boolean} true/false if the item exists.
- */
- has: function(name) {
- return name in this.data;
- },
- /**
- * Returns a dynamic property binding for the specified property name. This makes
- * it possible to sync the state of two properties in two ObservableObject instances.
- *
- * @method bind
- * @param {String} name Name of the property to sync with the property it's inserted to.
- * @return {tinymce.data.Binding} Data binding instance.
- */
- bind: function(name) {
- return Binding.create(this, name);
- },
- /**
- * Destroys the observable object and fires the "destroy"
- * event and clean up any internal resources.
- *
- * @method destroy
- */
- destroy: function() {
- this.fire('destroy');
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Selector.js
- /**
- * Selector.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*eslint no-nested-ternary:0 */
- /**
- * Selector engine, enables you to select controls by using CSS like expressions.
- * We currently only support basic CSS expressions to reduce the size of the core
- * and the ones we support should be enough for most cases.
- *
- * @example
- * Supported expressions:
- * element
- * element#name
- * element.class
- * element[attr]
- * element[attr*=value]
- * element[attr~=value]
- * element[attr!=value]
- * element[attr^=value]
- * element[attr$=value]
- * element:<state>
- * element:not(<expression>)
- * element:first
- * element:last
- * element:odd
- * element:even
- * element element
- * element > element
- *
- * @class tinymce.ui.Selector
- */
- define("tinymce/ui/Selector", [
- "tinymce/util/Class"
- ], function(Class) {
- "use strict";
- /**
- * Produces an array with a unique set of objects. It will not compare the values
- * but the references of the objects.
- *
- * @private
- * @method unqiue
- * @param {Array} array Array to make into an array with unique items.
- * @return {Array} Array with unique items.
- */
- function unique(array) {
- var uniqueItems = [], i = array.length, item;
- while (i--) {
- item = array[i];
- if (!item.__checked) {
- uniqueItems.push(item);
- item.__checked = 1;
- }
- }
- i = uniqueItems.length;
- while (i--) {
- delete uniqueItems[i].__checked;
- }
- return uniqueItems;
- }
- var expression = /^([\w\\*]+)?(?:#([\w\-\\]+))?(?:\.([\w\\\.]+))?(?:\[\@?([\w\\]+)([\^\$\*!~]?=)([\w\\]+)\])?(?:\:(.+))?/i;
- /*jshint maxlen:255 */
- /*eslint max-len:0 */
- var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
- whiteSpace = /^\s*|\s*$/g,
- Collection;
- var Selector = Class.extend({
- /**
- * Constructs a new Selector instance.
- *
- * @constructor
- * @method init
- * @param {String} selector CSS like selector expression.
- */
- init: function(selector) {
- var match = this.match;
- function compileNameFilter(name) {
- if (name) {
- name = name.toLowerCase();
- return function(item) {
- return name === '*' || item.type === name;
- };
- }
- }
- function compileIdFilter(id) {
- if (id) {
- return function(item) {
- return item._name === id;
- };
- }
- }
- function compileClassesFilter(classes) {
- if (classes) {
- classes = classes.split('.');
- return function(item) {
- var i = classes.length;
- while (i--) {
- if (!item.classes.contains(classes[i])) {
- return false;
- }
- }
- return true;
- };
- }
- }
- function compileAttrFilter(name, cmp, check) {
- if (name) {
- return function(item) {
- var value = item[name] ? item[name]() : '';
- return !cmp ? !!check :
- cmp === "=" ? value === check :
- cmp === "*=" ? value.indexOf(check) >= 0 :
- cmp === "~=" ? (" " + value + " ").indexOf(" " + check + " ") >= 0 :
- cmp === "!=" ? value != check :
- cmp === "^=" ? value.indexOf(check) === 0 :
- cmp === "$=" ? value.substr(value.length - check.length) === check :
- false;
- };
- }
- }
- function compilePsuedoFilter(name) {
- var notSelectors;
- if (name) {
- name = /(?:not\((.+)\))|(.+)/i.exec(name);
- if (!name[1]) {
- name = name[2];
- return function(item, index, length) {
- return name === 'first' ? index === 0 :
- name === 'last' ? index === length - 1 :
- name === 'even' ? index % 2 === 0 :
- name === 'odd' ? index % 2 === 1 :
- item[name] ? item[name]() :
- false;
- };
- }
- // Compile not expression
- notSelectors = parseChunks(name[1], []);
- return function(item) {
- return !match(item, notSelectors);
- };
- }
- }
- function compile(selector, filters, direct) {
- var parts;
- function add(filter) {
- if (filter) {
- filters.push(filter);
- }
- }
- // Parse expression into parts
- parts = expression.exec(selector.replace(whiteSpace, ''));
- add(compileNameFilter(parts[1]));
- add(compileIdFilter(parts[2]));
- add(compileClassesFilter(parts[3]));
- add(compileAttrFilter(parts[4], parts[5], parts[6]));
- add(compilePsuedoFilter(parts[7]));
- // Mark the filter with pseudo for performance
- filters.pseudo = !!parts[7];
- filters.direct = direct;
- return filters;
- }
- // Parser logic based on Sizzle by John Resig
- function parseChunks(selector, selectors) {
- var parts = [], extra, matches, i;
- do {
- chunker.exec("");
- matches = chunker.exec(selector);
- if (matches) {
- selector = matches[3];
- parts.push(matches[1]);
- if (matches[2]) {
- extra = matches[3];
- break;
- }
- }
- } while (matches);
- if (extra) {
- parseChunks(extra, selectors);
- }
- selector = [];
- for (i = 0; i < parts.length; i++) {
- if (parts[i] != '>') {
- selector.push(compile(parts[i], [], parts[i - 1] === '>'));
- }
- }
- selectors.push(selector);
- return selectors;
- }
- this._selectors = parseChunks(selector, []);
- },
- /**
- * Returns true/false if the selector matches the specified control.
- *
- * @method match
- * @param {tinymce.ui.Control} control Control to match against the selector.
- * @param {Array} selectors Optional array of selectors, mostly used internally.
- * @return {Boolean} true/false state if the control matches or not.
- */
- match: function(control, selectors) {
- var i, l, si, sl, selector, fi, fl, filters, index, length, siblings, count, item;
- selectors = selectors || this._selectors;
- for (i = 0, l = selectors.length; i < l; i++) {
- selector = selectors[i];
- sl = selector.length;
- item = control;
- count = 0;
- for (si = sl - 1; si >= 0; si--) {
- filters = selector[si];
- while (item) {
- // Find the index and length since a pseudo filter like :first needs it
- if (filters.pseudo) {
- siblings = item.parent().items();
- index = length = siblings.length;
- while (index--) {
- if (siblings[index] === item) {
- break;
- }
- }
- }
- for (fi = 0, fl = filters.length; fi < fl; fi++) {
- if (!filters[fi](item, index, length)) {
- fi = fl + 1;
- break;
- }
- }
- if (fi === fl) {
- count++;
- break;
- } else {
- // If it didn't match the right most expression then
- // break since it's no point looking at the parents
- if (si === sl - 1) {
- break;
- }
- }
- item = item.parent();
- }
- }
- // If we found all selectors then return true otherwise continue looking
- if (count === sl) {
- return true;
- }
- }
- return false;
- },
- /**
- * Returns a tinymce.ui.Collection with matches of the specified selector inside the specified container.
- *
- * @method find
- * @param {tinymce.ui.Control} container Container to look for items in.
- * @return {tinymce.ui.Collection} Collection with matched elements.
- */
- find: function(container) {
- var matches = [], i, l, selectors = this._selectors;
- function collect(items, selector, index) {
- var i, l, fi, fl, item, filters = selector[index];
- for (i = 0, l = items.length; i < l; i++) {
- item = items[i];
- // Run each filter against the item
- for (fi = 0, fl = filters.length; fi < fl; fi++) {
- if (!filters[fi](item, i, l)) {
- fi = fl + 1;
- break;
- }
- }
- // All filters matched the item
- if (fi === fl) {
- // Matched item is on the last expression like: panel toolbar [button]
- if (index == selector.length - 1) {
- matches.push(item);
- } else {
- // Collect next expression type
- if (item.items) {
- collect(item.items(), selector, index + 1);
- }
- }
- } else if (filters.direct) {
- return;
- }
- // Collect child items
- if (item.items) {
- collect(item.items(), selector, index);
- }
- }
- }
- if (container.items) {
- for (i = 0, l = selectors.length; i < l; i++) {
- collect(container.items(), selectors[i], 0);
- }
- // Unique the matches if needed
- if (l > 1) {
- matches = unique(matches);
- }
- }
- // Fix for circular reference
- if (!Collection) {
- // TODO: Fix me!
- Collection = Selector.Collection;
- }
- return new Collection(matches);
- }
- });
- return Selector;
- });
- // Included from: js/tinymce/classes/ui/Collection.js
- /**
- * Collection.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Control collection, this class contains control instances and it enables you to
- * perform actions on all the contained items. This is very similar to how jQuery works.
- *
- * @example
- * someCollection.show().disabled(true);
- *
- * @class tinymce.ui.Collection
- */
- define("tinymce/ui/Collection", [
- "tinymce/util/Tools",
- "tinymce/ui/Selector",
- "tinymce/util/Class"
- ], function(Tools, Selector, Class) {
- "use strict";
- var Collection, proto, push = Array.prototype.push, slice = Array.prototype.slice;
- proto = {
- /**
- * Current number of contained control instances.
- *
- * @field length
- * @type Number
- */
- length: 0,
- /**
- * Constructor for the collection.
- *
- * @constructor
- * @method init
- * @param {Array} items Optional array with items to add.
- */
- init: function(items) {
- if (items) {
- this.add(items);
- }
- },
- /**
- * Adds new items to the control collection.
- *
- * @method add
- * @param {Array} items Array if items to add to collection.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- add: function(items) {
- var self = this;
- // Force single item into array
- if (!Tools.isArray(items)) {
- if (items instanceof Collection) {
- self.add(items.toArray());
- } else {
- push.call(self, items);
- }
- } else {
- push.apply(self, items);
- }
- return self;
- },
- /**
- * Sets the contents of the collection. This will remove any existing items
- * and replace them with the ones specified in the input array.
- *
- * @method set
- * @param {Array} items Array with items to set into the Collection.
- * @return {tinymce.ui.Collection} Collection instance.
- */
- set: function(items) {
- var self = this, len = self.length, i;
- self.length = 0;
- self.add(items);
- // Remove old entries
- for (i = self.length; i < len; i++) {
- delete self[i];
- }
- return self;
- },
- /**
- * Filters the collection item based on the specified selector expression or selector function.
- *
- * @method filter
- * @param {String} selector Selector expression to filter items by.
- * @return {tinymce.ui.Collection} Collection containing the filtered items.
- */
- filter: function(selector) {
- var self = this, i, l, matches = [], item, match;
- // Compile string into selector expression
- if (typeof selector === "string") {
- selector = new Selector(selector);
- match = function(item) {
- return selector.match(item);
- };
- } else {
- // Use selector as matching function
- match = selector;
- }
- for (i = 0, l = self.length; i < l; i++) {
- item = self[i];
- if (match(item)) {
- matches.push(item);
- }
- }
- return new Collection(matches);
- },
- /**
- * Slices the items within the collection.
- *
- * @method slice
- * @param {Number} index Index to slice at.
- * @param {Number} len Optional length to slice.
- * @return {tinymce.ui.Collection} Current collection.
- */
- slice: function() {
- return new Collection(slice.apply(this, arguments));
- },
- /**
- * Makes the current collection equal to the specified index.
- *
- * @method eq
- * @param {Number} index Index of the item to set the collection to.
- * @return {tinymce.ui.Collection} Current collection.
- */
- eq: function(index) {
- return index === -1 ? this.slice(index) : this.slice(index, +index + 1);
- },
- /**
- * Executes the specified callback on each item in collection.
- *
- * @method each
- * @param {function} callback Callback to execute for each item in collection.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- each: function(callback) {
- Tools.each(this, callback);
- return this;
- },
- /**
- * Returns an JavaScript array object of the contents inside the collection.
- *
- * @method toArray
- * @return {Array} Array with all items from collection.
- */
- toArray: function() {
- return Tools.toArray(this);
- },
- /**
- * Finds the index of the specified control or return -1 if it isn't in the collection.
- *
- * @method indexOf
- * @param {Control} ctrl Control instance to look for.
- * @return {Number} Index of the specified control or -1.
- */
- indexOf: function(ctrl) {
- var self = this, i = self.length;
- while (i--) {
- if (self[i] === ctrl) {
- break;
- }
- }
- return i;
- },
- /**
- * Returns a new collection of the contents in reverse order.
- *
- * @method reverse
- * @return {tinymce.ui.Collection} Collection instance with reversed items.
- */
- reverse: function() {
- return new Collection(Tools.toArray(this).reverse());
- },
- /**
- * Returns true/false if the class exists or not.
- *
- * @method hasClass
- * @param {String} cls Class to check for.
- * @return {Boolean} true/false state if the class exists or not.
- */
- hasClass: function(cls) {
- return this[0] ? this[0].classes.contains(cls) : false;
- },
- /**
- * Sets/gets the specific property on the items in the collection. The same as executing control.<property>(<value>);
- *
- * @method prop
- * @param {String} name Property name to get/set.
- * @param {Object} value Optional object value to set.
- * @return {tinymce.ui.Collection} Current collection instance or value of the first item on a get operation.
- */
- prop: function(name, value) {
- var self = this, undef, item;
- if (value !== undef) {
- self.each(function(item) {
- if (item[name]) {
- item[name](value);
- }
- });
- return self;
- }
- item = self[0];
- if (item && item[name]) {
- return item[name]();
- }
- },
- /**
- * Executes the specific function name with optional arguments an all items in collection if it exists.
- *
- * @example collection.exec("myMethod", arg1, arg2, arg3);
- * @method exec
- * @param {String} name Name of the function to execute.
- * @param {Object} ... Multiple arguments to pass to each function.
- * @return {tinymce.ui.Collection} Current collection.
- */
- exec: function(name) {
- var self = this, args = Tools.toArray(arguments).slice(1);
- self.each(function(item) {
- if (item[name]) {
- item[name].apply(item, args);
- }
- });
- return self;
- },
- /**
- * Remove all items from collection and DOM.
- *
- * @method remove
- * @return {tinymce.ui.Collection} Current collection.
- */
- remove: function() {
- var i = this.length;
- while (i--) {
- this[i].remove();
- }
- return this;
- },
- /**
- * Adds a class to all items in the collection.
- *
- * @method addClass
- * @param {String} cls Class to add to each item.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- addClass: function(cls) {
- return this.each(function(item) {
- item.classes.add(cls);
- });
- },
- /**
- * Removes the specified class from all items in collection.
- *
- * @method removeClass
- * @param {String} cls Class to remove from each item.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- removeClass: function(cls) {
- return this.each(function(item) {
- item.classes.remove(cls);
- });
- }
- /**
- * Fires the specified event by name and arguments on the control. This will execute all
- * bound event handlers.
- *
- * @method fire
- * @param {String} name Name of the event to fire.
- * @param {Object} args Optional arguments to pass to the event.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- // fire: function(event, args) {}, -- Generated by code below
- /**
- * Binds a callback to the specified event. This event can both be
- * native browser events like "click" or custom ones like PostRender.
- *
- * The callback function will have two parameters the first one being the control that received the event
- * the second one will be the event object either the browsers native event object or a custom JS object.
- *
- * @method on
- * @param {String} name Name of the event to bind. For example "click".
- * @param {String/function} callback Callback function to execute ones the event occurs.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- // on: function(name, callback) {}, -- Generated by code below
- /**
- * Unbinds the specified event and optionally a specific callback. If you omit the name
- * parameter all event handlers will be removed. If you omit the callback all event handles
- * by the specified name will be removed.
- *
- * @method off
- * @param {String} name Optional name for the event to unbind.
- * @param {function} callback Optional callback function to unbind.
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- // off: function(name, callback) {}, -- Generated by code below
- /**
- * Shows the items in the current collection.
- *
- * @method show
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- // show: function() {}, -- Generated by code below
- /**
- * Hides the items in the current collection.
- *
- * @method hide
- * @return {tinymce.ui.Collection} Current collection instance.
- */
- // hide: function() {}, -- Generated by code below
- /**
- * Sets/gets the text contents of the items in the current collection.
- *
- * @method text
- * @return {tinymce.ui.Collection} Current collection instance or text value of the first item on a get operation.
- */
- // text: function(value) {}, -- Generated by code below
- /**
- * Sets/gets the name contents of the items in the current collection.
- *
- * @method name
- * @return {tinymce.ui.Collection} Current collection instance or name value of the first item on a get operation.
- */
- // name: function(value) {}, -- Generated by code below
- /**
- * Sets/gets the disabled state on the items in the current collection.
- *
- * @method disabled
- * @return {tinymce.ui.Collection} Current collection instance or disabled state of the first item on a get operation.
- */
- // disabled: function(state) {}, -- Generated by code below
- /**
- * Sets/gets the active state on the items in the current collection.
- *
- * @method active
- * @return {tinymce.ui.Collection} Current collection instance or active state of the first item on a get operation.
- */
- // active: function(state) {}, -- Generated by code below
- /**
- * Sets/gets the selected state on the items in the current collection.
- *
- * @method selected
- * @return {tinymce.ui.Collection} Current collection instance or selected state of the first item on a get operation.
- */
- // selected: function(state) {}, -- Generated by code below
- /**
- * Sets/gets the selected state on the items in the current collection.
- *
- * @method visible
- * @return {tinymce.ui.Collection} Current collection instance or visible state of the first item on a get operation.
- */
- // visible: function(state) {}, -- Generated by code below
- };
- // Extend tinymce.ui.Collection prototype with some generated control specific methods
- Tools.each('fire on off show hide append prepend before after reflow'.split(' '), function(name) {
- proto[name] = function() {
- var args = Tools.toArray(arguments);
- this.each(function(ctrl) {
- if (name in ctrl) {
- ctrl[name].apply(ctrl, args);
- }
- });
- return this;
- };
- });
- // Extend tinymce.ui.Collection prototype with some property methods
- Tools.each('text name disabled active selected checked visible parent value data'.split(' '), function(name) {
- proto[name] = function(value) {
- return this.prop(name, value);
- };
- });
- // Create class based on the new prototype
- Collection = Class.extend(proto);
- // Stick Collection into Selector to prevent circual references
- Selector.Collection = Collection;
- return Collection;
- });
- // Included from: js/tinymce/classes/ui/DomUtils.js
- /**
- * DomUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Private UI DomUtils proxy.
- *
- * @private
- * @class tinymce.ui.DomUtils
- */
- define("tinymce/ui/DomUtils", [
- "tinymce/util/Tools",
- "tinymce/dom/DOMUtils"
- ], function(Tools, DOMUtils) {
- "use strict";
- var count = 0;
- return {
- id: function() {
- return 'mceu_' + (count++);
- },
- create: function(name, attrs, children) {
- var elm = document.createElement(name);
- DOMUtils.DOM.setAttribs(elm, attrs);
- if (typeof children === 'string') {
- elm.innerHTML = children;
- } else {
- Tools.each(children, function(child) {
- if (child.nodeType) {
- elm.appendChild(child);
- }
- });
- }
- return elm;
- },
- createFragment: function(html) {
- return DOMUtils.DOM.createFragment(html);
- },
- getWindowSize: function() {
- return DOMUtils.DOM.getViewPort();
- },
- getSize: function(elm) {
- var width, height;
- if (elm.getBoundingClientRect) {
- var rect = elm.getBoundingClientRect();
- width = Math.max(rect.width || (rect.right - rect.left), elm.offsetWidth);
- height = Math.max(rect.height || (rect.bottom - rect.bottom), elm.offsetHeight);
- } else {
- width = elm.offsetWidth;
- height = elm.offsetHeight;
- }
- return {width: width, height: height};
- },
- getPos: function(elm, root) {
- return DOMUtils.DOM.getPos(elm, root);
- },
- getViewPort: function(win) {
- return DOMUtils.DOM.getViewPort(win);
- },
- get: function(id) {
- return document.getElementById(id);
- },
- addClass: function(elm, cls) {
- return DOMUtils.DOM.addClass(elm, cls);
- },
- removeClass: function(elm, cls) {
- return DOMUtils.DOM.removeClass(elm, cls);
- },
- hasClass: function(elm, cls) {
- return DOMUtils.DOM.hasClass(elm, cls);
- },
- toggleClass: function(elm, cls, state) {
- return DOMUtils.DOM.toggleClass(elm, cls, state);
- },
- css: function(elm, name, value) {
- return DOMUtils.DOM.setStyle(elm, name, value);
- },
- getRuntimeStyle: function(elm, name) {
- return DOMUtils.DOM.getStyle(elm, name, true);
- },
- on: function(target, name, callback, scope) {
- return DOMUtils.DOM.bind(target, name, callback, scope);
- },
- off: function(target, name, callback) {
- return DOMUtils.DOM.unbind(target, name, callback);
- },
- fire: function(target, name, args) {
- return DOMUtils.DOM.fire(target, name, args);
- },
- innerHtml: function(elm, html) {
- // Workaround for <div> in <p> bug on IE 8 #6178
- DOMUtils.DOM.setHTML(elm, html);
- }
- };
- });
- // Included from: js/tinymce/classes/ui/BoxUtils.js
- /**
- * BoxUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility class for box parsing and measuring.
- *
- * @private
- * @class tinymce.ui.BoxUtils
- */
- define("tinymce/ui/BoxUtils", [
- ], function() {
- "use strict";
- return {
- /**
- * Parses the specified box value. A box value contains 1-4 properties in clockwise order.
- *
- * @method parseBox
- * @param {String/Number} value Box value "0 1 2 3" or "0" etc.
- * @return {Object} Object with top/right/bottom/left properties.
- * @private
- */
- parseBox: function(value) {
- var len, radix = 10;
- if (!value) {
- return;
- }
- if (typeof value === "number") {
- value = value || 0;
- return {
- top: value,
- left: value,
- bottom: value,
- right: value
- };
- }
- value = value.split(' ');
- len = value.length;
- if (len === 1) {
- value[1] = value[2] = value[3] = value[0];
- } else if (len === 2) {
- value[2] = value[0];
- value[3] = value[1];
- } else if (len === 3) {
- value[3] = value[1];
- }
- return {
- top: parseInt(value[0], radix) || 0,
- right: parseInt(value[1], radix) || 0,
- bottom: parseInt(value[2], radix) || 0,
- left: parseInt(value[3], radix) || 0
- };
- },
- measureBox: function(elm, prefix) {
- function getStyle(name) {
- var defaultView = document.defaultView;
- if (defaultView) {
- // Remove camelcase
- name = name.replace(/[A-Z]/g, function(a) {
- return '-' + a;
- });
- return defaultView.getComputedStyle(elm, null).getPropertyValue(name);
- }
- return elm.currentStyle[name];
- }
- function getSide(name) {
- var val = parseFloat(getStyle(name), 10);
- return isNaN(val) ? 0 : val;
- }
- return {
- top: getSide(prefix + "TopWidth"),
- right: getSide(prefix + "RightWidth"),
- bottom: getSide(prefix + "BottomWidth"),
- left: getSide(prefix + "LeftWidth")
- };
- }
- };
- });
- // Included from: js/tinymce/classes/ui/ClassList.js
- /**
- * ClassList.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Handles adding and removal of classes.
- *
- * @private
- * @class tinymce.ui.ClassList
- */
- define("tinymce/ui/ClassList", [
- "tinymce/util/Tools"
- ], function(Tools) {
- "use strict";
- function noop() {
- }
- /**
- * Constructs a new class list the specified onchange
- * callback will be executed when the class list gets modifed.
- *
- * @constructor ClassList
- * @param {function} onchange Onchange callback to be executed.
- */
- function ClassList(onchange) {
- this.cls = [];
- this.cls._map = {};
- this.onchange = onchange || noop;
- this.prefix = '';
- }
- Tools.extend(ClassList.prototype, {
- /**
- * Adds a new class to the class list.
- *
- * @method add
- * @param {String} cls Class to be added.
- * @return {tinymce.ui.ClassList} Current class list instance.
- */
- add: function(cls) {
- if (cls && !this.contains(cls)) {
- this.cls._map[cls] = true;
- this.cls.push(cls);
- this._change();
- }
- return this;
- },
- /**
- * Removes the specified class from the class list.
- *
- * @method remove
- * @param {String} cls Class to be removed.
- * @return {tinymce.ui.ClassList} Current class list instance.
- */
- remove: function(cls) {
- if (this.contains(cls)) {
- for (var i = 0; i < this.cls.length; i++) {
- if (this.cls[i] === cls) {
- break;
- }
- }
- this.cls.splice(i, 1);
- delete this.cls._map[cls];
- this._change();
- }
- return this;
- },
- /**
- * Toggles a class in the class list.
- *
- * @method toggle
- * @param {String} cls Class to be added/removed.
- * @param {Boolean} state Optional state if it should be added/removed.
- * @return {tinymce.ui.ClassList} Current class list instance.
- */
- toggle: function(cls, state) {
- var curState = this.contains(cls);
- if (curState !== state) {
- if (curState) {
- this.remove(cls);
- } else {
- this.add(cls);
- }
- this._change();
- }
- return this;
- },
- /**
- * Returns true if the class list has the specified class.
- *
- * @method contains
- * @param {String} cls Class to look for.
- * @return {Boolean} true/false if the class exists or not.
- */
- contains: function(cls) {
- return !!this.cls._map[cls];
- },
- /**
- * Returns a space separated list of classes.
- *
- * @method toString
- * @return {String} Space separated list of classes.
- */
- _change: function() {
- delete this.clsValue;
- this.onchange.call(this);
- }
- });
- // IE 8 compatibility
- ClassList.prototype.toString = function() {
- var value;
- if (this.clsValue) {
- return this.clsValue;
- }
- value = '';
- for (var i = 0; i < this.cls.length; i++) {
- if (i > 0) {
- value += ' ';
- }
- value += this.prefix + this.cls[i];
- }
- return value;
- };
- return ClassList;
- });
- // Included from: js/tinymce/classes/ui/ReflowQueue.js
- /**
- * ReflowQueue.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class will automatically reflow controls on the next animation frame within a few milliseconds on older browsers.
- * If the user manually reflows then the automatic reflow will be cancelled. This class is used internally when various control states
- * changes that triggers a reflow.
- *
- * @class tinymce.ui.ReflowQueue
- * @static
- */
- define("tinymce/ui/ReflowQueue", [
- "tinymce/util/Delay"
- ], function(Delay) {
- var dirtyCtrls = {}, animationFrameRequested;
- return {
- /**
- * Adds a control to the next automatic reflow call. This is the control that had a state
- * change for example if the control was hidden/shown.
- *
- * @method add
- * @param {tinymce.ui.Control} ctrl Control to add to queue.
- */
- add: function(ctrl) {
- var parent = ctrl.parent();
- if (parent) {
- if (!parent._layout || parent._layout.isNative()) {
- return;
- }
- if (!dirtyCtrls[parent._id]) {
- dirtyCtrls[parent._id] = parent;
- }
- if (!animationFrameRequested) {
- animationFrameRequested = true;
- Delay.requestAnimationFrame(function() {
- var id, ctrl;
- animationFrameRequested = false;
- for (id in dirtyCtrls) {
- ctrl = dirtyCtrls[id];
- if (ctrl.state.get('rendered')) {
- ctrl.reflow();
- }
- }
- dirtyCtrls = {};
- }, document.body);
- }
- }
- },
- /**
- * Removes the specified control from the automatic reflow. This will happen when for example the user
- * manually triggers a reflow.
- *
- * @method remove
- * @param {tinymce.ui.Control} ctrl Control to remove from queue.
- */
- remove: function(ctrl) {
- if (dirtyCtrls[ctrl._id]) {
- delete dirtyCtrls[ctrl._id];
- }
- }
- };
- });
- // Included from: js/tinymce/classes/ui/Control.js
- /**
- * Control.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*eslint consistent-this:0 */
- /**
- * This is the base class for all controls and containers. All UI control instances inherit
- * from this one as it has the base logic needed by all of them.
- *
- * @class tinymce.ui.Control
- */
- define("tinymce/ui/Control", [
- "tinymce/util/Class",
- "tinymce/util/Tools",
- "tinymce/util/EventDispatcher",
- "tinymce/data/ObservableObject",
- "tinymce/ui/Collection",
- "tinymce/ui/DomUtils",
- "tinymce/dom/DomQuery",
- "tinymce/ui/BoxUtils",
- "tinymce/ui/ClassList",
- "tinymce/ui/ReflowQueue"
- ], function(Class, Tools, EventDispatcher, ObservableObject, Collection, DomUtils, $, BoxUtils, ClassList, ReflowQueue) {
- "use strict";
- var hasMouseWheelEventSupport = "onmousewheel" in document;
- var hasWheelEventSupport = false;
- var classPrefix = "mce-";
- var Control, idCounter = 0;
- var proto = {
- Statics: {
- classPrefix: classPrefix
- },
- isRtl: function() {
- return Control.rtl;
- },
- /**
- * Class/id prefix to use for all controls.
- *
- * @final
- * @field {String} classPrefix
- */
- classPrefix: classPrefix,
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} style Style CSS properties to add.
- * @setting {String} border Border box values example: 1 1 1 1
- * @setting {String} padding Padding box values example: 1 1 1 1
- * @setting {String} margin Margin box values example: 1 1 1 1
- * @setting {Number} minWidth Minimal width for the control.
- * @setting {Number} minHeight Minimal height for the control.
- * @setting {String} classes Space separated list of classes to add.
- * @setting {String} role WAI-ARIA role to use for control.
- * @setting {Boolean} hidden Is the control hidden by default.
- * @setting {Boolean} disabled Is the control disabled by default.
- * @setting {String} name Name of the control instance.
- */
- init: function(settings) {
- var self = this, classes, defaultClasses;
- function applyClasses(classes) {
- var i;
- classes = classes.split(' ');
- for (i = 0; i < classes.length; i++) {
- self.classes.add(classes[i]);
- }
- }
- self.settings = settings = Tools.extend({}, self.Defaults, settings);
- // Initial states
- self._id = settings.id || ('mceu_' + (idCounter++));
- self._aria = {role: settings.role};
- self._elmCache = {};
- self.$ = $;
- self.state = new ObservableObject({
- visible: true,
- active: false,
- disabled: false,
- value: ''
- });
- self.data = new ObservableObject(settings.data);
- self.classes = new ClassList(function() {
- if (self.state.get('rendered')) {
- self.getEl().className = this.toString();
- }
- });
- self.classes.prefix = self.classPrefix;
- // Setup classes
- classes = settings.classes;
- if (classes) {
- if (self.Defaults) {
- defaultClasses = self.Defaults.classes;
- if (defaultClasses && classes != defaultClasses) {
- applyClasses(defaultClasses);
- }
- }
- applyClasses(classes);
- }
- Tools.each('title text name visible disabled active value'.split(' '), function(name) {
- if (name in settings) {
- self[name](settings[name]);
- }
- });
- self.on('click', function() {
- if (self.disabled()) {
- return false;
- }
- });
- /**
- * Name/value object with settings for the current control.
- *
- * @field {Object} settings
- */
- self.settings = settings;
- self.borderBox = BoxUtils.parseBox(settings.border);
- self.paddingBox = BoxUtils.parseBox(settings.padding);
- self.marginBox = BoxUtils.parseBox(settings.margin);
- if (settings.hidden) {
- self.hide();
- }
- },
- // Will generate getter/setter methods for these properties
- Properties: 'parent,name',
- /**
- * Returns the root element to render controls into.
- *
- * @method getContainerElm
- * @return {Element} HTML DOM element to render into.
- */
- getContainerElm: function() {
- return document.body;
- },
- /**
- * Returns a control instance for the current DOM element.
- *
- * @method getParentCtrl
- * @param {Element} elm HTML dom element to get parent control from.
- * @return {tinymce.ui.Control} Control instance or undefined.
- */
- getParentCtrl: function(elm) {
- var ctrl, lookup = this.getRoot().controlIdLookup;
- while (elm && lookup) {
- ctrl = lookup[elm.id];
- if (ctrl) {
- break;
- }
- elm = elm.parentNode;
- }
- return ctrl;
- },
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, settings = self.settings, borderBox, layoutRect;
- var elm = self.getEl(), width, height, minWidth, minHeight, autoResize;
- var startMinWidth, startMinHeight, initialSize;
- // Measure the current element
- borderBox = self.borderBox = self.borderBox || BoxUtils.measureBox(elm, 'border');
- self.paddingBox = self.paddingBox || BoxUtils.measureBox(elm, 'padding');
- self.marginBox = self.marginBox || BoxUtils.measureBox(elm, 'margin');
- initialSize = DomUtils.getSize(elm);
- // Setup minWidth/minHeight and width/height
- startMinWidth = settings.minWidth;
- startMinHeight = settings.minHeight;
- minWidth = startMinWidth || initialSize.width;
- minHeight = startMinHeight || initialSize.height;
- width = settings.width;
- height = settings.height;
- autoResize = settings.autoResize;
- autoResize = typeof autoResize != "undefined" ? autoResize : !width && !height;
- width = width || minWidth;
- height = height || minHeight;
- var deltaW = borderBox.left + borderBox.right;
- var deltaH = borderBox.top + borderBox.bottom;
- var maxW = settings.maxWidth || 0xFFFF;
- var maxH = settings.maxHeight || 0xFFFF;
- // Setup initial layout rect
- self._layoutRect = layoutRect = {
- x: settings.x || 0,
- y: settings.y || 0,
- w: width,
- h: height,
- deltaW: deltaW,
- deltaH: deltaH,
- contentW: width - deltaW,
- contentH: height - deltaH,
- innerW: width - deltaW,
- innerH: height - deltaH,
- startMinWidth: startMinWidth || 0,
- startMinHeight: startMinHeight || 0,
- minW: Math.min(minWidth, maxW),
- minH: Math.min(minHeight, maxH),
- maxW: maxW,
- maxH: maxH,
- autoResize: autoResize,
- scrollW: 0
- };
- self._lastLayoutRect = {};
- return layoutRect;
- },
- /**
- * Getter/setter for the current layout rect.
- *
- * @method layoutRect
- * @param {Object} [newRect] Optional new layout rect.
- * @return {tinymce.ui.Control/Object} Current control or rect object.
- */
- layoutRect: function(newRect) {
- var self = this, curRect = self._layoutRect, lastLayoutRect, size, deltaWidth, deltaHeight, undef, repaintControls;
- // Initialize default layout rect
- if (!curRect) {
- curRect = self.initLayoutRect();
- }
- // Set new rect values
- if (newRect) {
- // Calc deltas between inner and outer sizes
- deltaWidth = curRect.deltaW;
- deltaHeight = curRect.deltaH;
- // Set x position
- if (newRect.x !== undef) {
- curRect.x = newRect.x;
- }
- // Set y position
- if (newRect.y !== undef) {
- curRect.y = newRect.y;
- }
- // Set minW
- if (newRect.minW !== undef) {
- curRect.minW = newRect.minW;
- }
- // Set minH
- if (newRect.minH !== undef) {
- curRect.minH = newRect.minH;
- }
- // Set new width and calculate inner width
- size = newRect.w;
- if (size !== undef) {
- size = size < curRect.minW ? curRect.minW : size;
- size = size > curRect.maxW ? curRect.maxW : size;
- curRect.w = size;
- curRect.innerW = size - deltaWidth;
- }
- // Set new height and calculate inner height
- size = newRect.h;
- if (size !== undef) {
- size = size < curRect.minH ? curRect.minH : size;
- size = size > curRect.maxH ? curRect.maxH : size;
- curRect.h = size;
- curRect.innerH = size - deltaHeight;
- }
- // Set new inner width and calculate width
- size = newRect.innerW;
- if (size !== undef) {
- size = size < curRect.minW - deltaWidth ? curRect.minW - deltaWidth : size;
- size = size > curRect.maxW - deltaWidth ? curRect.maxW - deltaWidth : size;
- curRect.innerW = size;
- curRect.w = size + deltaWidth;
- }
- // Set new height and calculate inner height
- size = newRect.innerH;
- if (size !== undef) {
- size = size < curRect.minH - deltaHeight ? curRect.minH - deltaHeight : size;
- size = size > curRect.maxH - deltaHeight ? curRect.maxH - deltaHeight : size;
- curRect.innerH = size;
- curRect.h = size + deltaHeight;
- }
- // Set new contentW
- if (newRect.contentW !== undef) {
- curRect.contentW = newRect.contentW;
- }
- // Set new contentH
- if (newRect.contentH !== undef) {
- curRect.contentH = newRect.contentH;
- }
- // Compare last layout rect with the current one to see if we need to repaint or not
- lastLayoutRect = self._lastLayoutRect;
- if (lastLayoutRect.x !== curRect.x || lastLayoutRect.y !== curRect.y ||
- lastLayoutRect.w !== curRect.w || lastLayoutRect.h !== curRect.h) {
- repaintControls = Control.repaintControls;
- if (repaintControls) {
- if (repaintControls.map && !repaintControls.map[self._id]) {
- repaintControls.push(self);
- repaintControls.map[self._id] = true;
- }
- }
- lastLayoutRect.x = curRect.x;
- lastLayoutRect.y = curRect.y;
- lastLayoutRect.w = curRect.w;
- lastLayoutRect.h = curRect.h;
- }
- return self;
- }
- return curRect;
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, style, bodyStyle, bodyElm, rect, borderBox;
- var borderW, borderH, lastRepaintRect, round, value;
- // Use Math.round on all values on IE < 9
- round = !document.createRange ? Math.round : function(value) {
- return value;
- };
- style = self.getEl().style;
- rect = self._layoutRect;
- lastRepaintRect = self._lastRepaintRect || {};
- borderBox = self.borderBox;
- borderW = borderBox.left + borderBox.right;
- borderH = borderBox.top + borderBox.bottom;
- if (rect.x !== lastRepaintRect.x) {
- style.left = round(rect.x) + 'px';
- lastRepaintRect.x = rect.x;
- }
- if (rect.y !== lastRepaintRect.y) {
- style.top = round(rect.y) + 'px';
- lastRepaintRect.y = rect.y;
- }
- if (rect.w !== lastRepaintRect.w) {
- value = round(rect.w - borderW);
- style.width = (value >= 0 ? value : 0) + 'px';
- lastRepaintRect.w = rect.w;
- }
- if (rect.h !== lastRepaintRect.h) {
- value = round(rect.h - borderH);
- style.height = (value >= 0 ? value : 0) + 'px';
- lastRepaintRect.h = rect.h;
- }
- // Update body if needed
- if (self._hasBody && rect.innerW !== lastRepaintRect.innerW) {
- value = round(rect.innerW);
- bodyElm = self.getEl('body');
- if (bodyElm) {
- bodyStyle = bodyElm.style;
- bodyStyle.width = (value >= 0 ? value : 0) + 'px';
- }
- lastRepaintRect.innerW = rect.innerW;
- }
- if (self._hasBody && rect.innerH !== lastRepaintRect.innerH) {
- value = round(rect.innerH);
- bodyElm = bodyElm || self.getEl('body');
- if (bodyElm) {
- bodyStyle = bodyStyle || bodyElm.style;
- bodyStyle.height = (value >= 0 ? value : 0) + 'px';
- }
- lastRepaintRect.innerH = rect.innerH;
- }
- self._lastRepaintRect = lastRepaintRect;
- self.fire('repaint', {}, false);
- },
- /**
- * Updates the controls layout rect by re-measuing it.
- */
- updateLayoutRect: function() {
- var self = this;
- self.parent()._lastRect = null;
- DomUtils.css(self.getEl(), {width: '', height: ''});
- self._layoutRect = self._lastRepaintRect = self._lastLayoutRect = null;
- self.initLayoutRect();
- },
- /**
- * Binds a callback to the specified event. This event can both be
- * native browser events like "click" or custom ones like PostRender.
- *
- * The callback function will be passed a DOM event like object that enables yout do stop propagation.
- *
- * @method on
- * @param {String} name Name of the event to bind. For example "click".
- * @param {String/function} callback Callback function to execute ones the event occurs.
- * @return {tinymce.ui.Control} Current control object.
- */
- on: function(name, callback) {
- var self = this;
- function resolveCallbackName(name) {
- var callback, scope;
- if (typeof name != 'string') {
- return name;
- }
- return function(e) {
- if (!callback) {
- self.parentsAndSelf().each(function(ctrl) {
- var callbacks = ctrl.settings.callbacks;
- if (callbacks && (callback = callbacks[name])) {
- scope = ctrl;
- return false;
- }
- });
- }
- if (!callback) {
- e.action = name;
- this.fire('execute', e);
- return;
- }
- return callback.call(scope, e);
- };
- }
- getEventDispatcher(self).on(name, resolveCallbackName(callback));
- return self;
- },
- /**
- * Unbinds the specified event and optionally a specific callback. If you omit the name
- * parameter all event handlers will be removed. If you omit the callback all event handles
- * by the specified name will be removed.
- *
- * @method off
- * @param {String} [name] Name for the event to unbind.
- * @param {function} [callback] Callback function to unbind.
- * @return {tinymce.ui.Control} Current control object.
- */
- off: function(name, callback) {
- getEventDispatcher(this).off(name, callback);
- return this;
- },
- /**
- * Fires the specified event by name and arguments on the control. This will execute all
- * bound event handlers.
- *
- * @method fire
- * @param {String} name Name of the event to fire.
- * @param {Object} [args] Arguments to pass to the event.
- * @param {Boolean} [bubble] Value to control bubbling. Defaults to true.
- * @return {Object} Current arguments object.
- */
- fire: function(name, args, bubble) {
- var self = this;
- args = args || {};
- if (!args.control) {
- args.control = self;
- }
- args = getEventDispatcher(self).fire(name, args);
- // Bubble event up to parents
- if (bubble !== false && self.parent) {
- var parent = self.parent();
- while (parent && !args.isPropagationStopped()) {
- parent.fire(name, args, false);
- parent = parent.parent();
- }
- }
- return args;
- },
- /**
- * Returns true/false if the specified event has any listeners.
- *
- * @method hasEventListeners
- * @param {String} name Name of the event to check for.
- * @return {Boolean} True/false state if the event has listeners.
- */
- hasEventListeners: function(name) {
- return getEventDispatcher(this).has(name);
- },
- /**
- * Returns a control collection with all parent controls.
- *
- * @method parents
- * @param {String} selector Optional selector expression to find parents.
- * @return {tinymce.ui.Collection} Collection with all parent controls.
- */
- parents: function(selector) {
- var self = this, ctrl, parents = new Collection();
- // Add each parent to collection
- for (ctrl = self.parent(); ctrl; ctrl = ctrl.parent()) {
- parents.add(ctrl);
- }
- // Filter away everything that doesn't match the selector
- if (selector) {
- parents = parents.filter(selector);
- }
- return parents;
- },
- /**
- * Returns the current control and it's parents.
- *
- * @method parentsAndSelf
- * @param {String} selector Optional selector expression to find parents.
- * @return {tinymce.ui.Collection} Collection with all parent controls.
- */
- parentsAndSelf: function(selector) {
- return new Collection(this).add(this.parents(selector));
- },
- /**
- * Returns the control next to the current control.
- *
- * @method next
- * @return {tinymce.ui.Control} Next control instance.
- */
- next: function() {
- var parentControls = this.parent().items();
- return parentControls[parentControls.indexOf(this) + 1];
- },
- /**
- * Returns the control previous to the current control.
- *
- * @method prev
- * @return {tinymce.ui.Control} Previous control instance.
- */
- prev: function() {
- var parentControls = this.parent().items();
- return parentControls[parentControls.indexOf(this) - 1];
- },
- /**
- * Sets the inner HTML of the control element.
- *
- * @method innerHtml
- * @param {String} html Html string to set as inner html.
- * @return {tinymce.ui.Control} Current control object.
- */
- innerHtml: function(html) {
- this.$el.html(html);
- return this;
- },
- /**
- * Returns the control DOM element or sub element.
- *
- * @method getEl
- * @param {String} [suffix] Suffix to get element by.
- * @return {Element} HTML DOM element for the current control or it's children.
- */
- getEl: function(suffix) {
- var id = suffix ? this._id + '-' + suffix : this._id;
- if (!this._elmCache[id]) {
- this._elmCache[id] = $('#' + id)[0];
- }
- return this._elmCache[id];
- },
- /**
- * Sets the visible state to true.
- *
- * @method show
- * @return {tinymce.ui.Control} Current control instance.
- */
- show: function() {
- return this.visible(true);
- },
- /**
- * Sets the visible state to false.
- *
- * @method hide
- * @return {tinymce.ui.Control} Current control instance.
- */
- hide: function() {
- return this.visible(false);
- },
- /**
- * Focuses the current control.
- *
- * @method focus
- * @return {tinymce.ui.Control} Current control instance.
- */
- focus: function() {
- try {
- this.getEl().focus();
- } catch (ex) {
- // Ignore IE error
- }
- return this;
- },
- /**
- * Blurs the current control.
- *
- * @method blur
- * @return {tinymce.ui.Control} Current control instance.
- */
- blur: function() {
- this.getEl().blur();
- return this;
- },
- /**
- * Sets the specified aria property.
- *
- * @method aria
- * @param {String} name Name of the aria property to set.
- * @param {String} value Value of the aria property.
- * @return {tinymce.ui.Control} Current control instance.
- */
- aria: function(name, value) {
- var self = this, elm = self.getEl(self.ariaTarget);
- if (typeof value === "undefined") {
- return self._aria[name];
- }
- self._aria[name] = value;
- if (self.state.get('rendered')) {
- elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
- }
- return self;
- },
- /**
- * Encodes the specified string with HTML entities. It will also
- * translate the string to different languages.
- *
- * @method encode
- * @param {String/Object/Array} text Text to entity encode.
- * @param {Boolean} [translate=true] False if the contents shouldn't be translated.
- * @return {String} Encoded and possible traslated string.
- */
- encode: function(text, translate) {
- if (translate !== false) {
- text = this.translate(text);
- }
- return (text || '').replace(/[&<>"]/g, function(match) {
- return '&#' + match.charCodeAt(0) + ';';
- });
- },
- /**
- * Returns the translated string.
- *
- * @method translate
- * @param {String} text Text to translate.
- * @return {String} Translated string or the same as the input.
- */
- translate: function(text) {
- return Control.translate ? Control.translate(text) : text;
- },
- /**
- * Adds items before the current control.
- *
- * @method before
- * @param {Array/tinymce.ui.Collection} items Array of items to prepend before this control.
- * @return {tinymce.ui.Control} Current control instance.
- */
- before: function(items) {
- var self = this, parent = self.parent();
- if (parent) {
- parent.insert(items, parent.items().indexOf(self), true);
- }
- return self;
- },
- /**
- * Adds items after the current control.
- *
- * @method after
- * @param {Array/tinymce.ui.Collection} items Array of items to append after this control.
- * @return {tinymce.ui.Control} Current control instance.
- */
- after: function(items) {
- var self = this, parent = self.parent();
- if (parent) {
- parent.insert(items, parent.items().indexOf(self));
- }
- return self;
- },
- /**
- * Removes the current control from DOM and from UI collections.
- *
- * @method remove
- * @return {tinymce.ui.Control} Current control instance.
- */
- remove: function() {
- var self = this, elm = self.getEl(), parent = self.parent(), newItems, i;
- if (self.items) {
- var controls = self.items().toArray();
- i = controls.length;
- while (i--) {
- controls[i].remove();
- }
- }
- if (parent && parent.items) {
- newItems = [];
- parent.items().each(function(item) {
- if (item !== self) {
- newItems.push(item);
- }
- });
- parent.items().set(newItems);
- parent._lastRect = null;
- }
- if (self._eventsRoot && self._eventsRoot == self) {
- $(elm).off();
- }
- var lookup = self.getRoot().controlIdLookup;
- if (lookup) {
- delete lookup[self._id];
- }
- if (elm && elm.parentNode) {
- elm.parentNode.removeChild(elm);
- }
- self.state.set('rendered', false);
- self.state.destroy();
- self.fire('remove');
- return self;
- },
- /**
- * Renders the control before the specified element.
- *
- * @method renderBefore
- * @param {Element} elm Element to render before.
- * @return {tinymce.ui.Control} Current control instance.
- */
- renderBefore: function(elm) {
- $(elm).before(this.renderHtml());
- this.postRender();
- return this;
- },
- /**
- * Renders the control to the specified element.
- *
- * @method renderBefore
- * @param {Element} elm Element to render to.
- * @return {tinymce.ui.Control} Current control instance.
- */
- renderTo: function(elm) {
- $(elm || this.getContainerElm()).append(this.renderHtml());
- this.postRender();
- return this;
- },
- preRender: function() {
- },
- render: function() {
- },
- renderHtml: function() {
- return '<div id="' + this._id + '" class="' + this.classes + '"></div>';
- },
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.Control} Current control instance.
- */
- postRender: function() {
- var self = this, settings = self.settings, elm, box, parent, name, parentEventsRoot;
- self.$el = $(self.getEl());
- self.state.set('rendered', true);
- // Bind on<event> settings
- for (name in settings) {
- if (name.indexOf("on") === 0) {
- self.on(name.substr(2), settings[name]);
- }
- }
- if (self._eventsRoot) {
- for (parent = self.parent(); !parentEventsRoot && parent; parent = parent.parent()) {
- parentEventsRoot = parent._eventsRoot;
- }
- if (parentEventsRoot) {
- for (name in parentEventsRoot._nativeEvents) {
- self._nativeEvents[name] = true;
- }
- }
- }
- bindPendingEvents(self);
- if (settings.style) {
- elm = self.getEl();
- if (elm) {
- elm.setAttribute('style', settings.style);
- elm.style.cssText = settings.style;
- }
- }
- if (self.settings.border) {
- box = self.borderBox;
- self.$el.css({
- 'border-top-width': box.top,
- 'border-right-width': box.right,
- 'border-bottom-width': box.bottom,
- 'border-left-width': box.left
- });
- }
- // Add instance to lookup
- var root = self.getRoot();
- if (!root.controlIdLookup) {
- root.controlIdLookup = {};
- }
- root.controlIdLookup[self._id] = self;
- for (var key in self._aria) {
- self.aria(key, self._aria[key]);
- }
- if (self.state.get('visible') === false) {
- self.getEl().style.display = 'none';
- }
- self.bindStates();
- self.state.on('change:visible', function(e) {
- var state = e.value, parentCtrl;
- if (self.state.get('rendered')) {
- self.getEl().style.display = state === false ? 'none' : '';
- // Need to force a reflow here on IE 8
- self.getEl().getBoundingClientRect();
- }
- // Parent container needs to reflow
- parentCtrl = self.parent();
- if (parentCtrl) {
- parentCtrl._lastRect = null;
- }
- self.fire(state ? 'show' : 'hide');
- ReflowQueue.add(self);
- });
- self.fire('postrender', {}, false);
- },
- bindStates: function() {
- },
- /**
- * Scrolls the current control into view.
- *
- * @method scrollIntoView
- * @param {String} align Alignment in view top|center|bottom.
- * @return {tinymce.ui.Control} Current control instance.
- */
- scrollIntoView: function(align) {
- function getOffset(elm, rootElm) {
- var x, y, parent = elm;
- x = y = 0;
- while (parent && parent != rootElm && parent.nodeType) {
- x += parent.offsetLeft || 0;
- y += parent.offsetTop || 0;
- parent = parent.offsetParent;
- }
- return {x: x, y: y};
- }
- var elm = this.getEl(), parentElm = elm.parentNode;
- var x, y, width, height, parentWidth, parentHeight;
- var pos = getOffset(elm, parentElm);
- x = pos.x;
- y = pos.y;
- width = elm.offsetWidth;
- height = elm.offsetHeight;
- parentWidth = parentElm.clientWidth;
- parentHeight = parentElm.clientHeight;
- if (align == "end") {
- x -= parentWidth - width;
- y -= parentHeight - height;
- } else if (align == "center") {
- x -= (parentWidth / 2) - (width / 2);
- y -= (parentHeight / 2) - (height / 2);
- }
- parentElm.scrollLeft = x;
- parentElm.scrollTop = y;
- return this;
- },
- getRoot: function() {
- var ctrl = this, rootControl, parents = [];
- while (ctrl) {
- if (ctrl.rootControl) {
- rootControl = ctrl.rootControl;
- break;
- }
- parents.push(ctrl);
- rootControl = ctrl;
- ctrl = ctrl.parent();
- }
- if (!rootControl) {
- rootControl = this;
- }
- var i = parents.length;
- while (i--) {
- parents[i].rootControl = rootControl;
- }
- return rootControl;
- },
- /**
- * Reflows the current control and it's parents.
- * This should be used after you for example append children to the current control so
- * that the layout managers know that they need to reposition everything.
- *
- * @example
- * container.append({type: 'button', text: 'My button'}).reflow();
- *
- * @method reflow
- * @return {tinymce.ui.Control} Current control instance.
- */
- reflow: function() {
- ReflowQueue.remove(this);
- var parent = this.parent();
- if (parent._layout && !parent._layout.isNative()) {
- parent.reflow();
- }
- return this;
- }
- /**
- * Sets/gets the parent container for the control.
- *
- * @method parent
- * @param {tinymce.ui.Container} parent Optional parent to set.
- * @return {tinymce.ui.Control} Parent control or the current control on a set action.
- */
- // parent: function(parent) {} -- Generated
- /**
- * Sets/gets the text for the control.
- *
- * @method text
- * @param {String} value Value to set to control.
- * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
- */
- // text: function(value) {} -- Generated
- /**
- * Sets/gets the disabled state on the control.
- *
- * @method disabled
- * @param {Boolean} state Value to set to control.
- * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
- */
- // disabled: function(state) {} -- Generated
- /**
- * Sets/gets the active for the control.
- *
- * @method active
- * @param {Boolean} state Value to set to control.
- * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
- */
- // active: function(state) {} -- Generated
- /**
- * Sets/gets the name for the control.
- *
- * @method name
- * @param {String} value Value to set to control.
- * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
- */
- // name: function(value) {} -- Generated
- /**
- * Sets/gets the title for the control.
- *
- * @method title
- * @param {String} value Value to set to control.
- * @return {String/tinymce.ui.Control} Current control on a set operation or current value on a get.
- */
- // title: function(value) {} -- Generated
- /**
- * Sets/gets the visible for the control.
- *
- * @method visible
- * @param {Boolean} state Value to set to control.
- * @return {Boolean/tinymce.ui.Control} Current control on a set operation or current state on a get.
- */
- // visible: function(value) {} -- Generated
- };
- /**
- * Setup state properties.
- */
- Tools.each('text title visible disabled active value'.split(' '), function(name) {
- proto[name] = function(value) {
- if (arguments.length === 0) {
- return this.state.get(name);
- }
- if (typeof value != "undefined") {
- this.state.set(name, value);
- }
- return this;
- };
- });
- Control = Class.extend(proto);
- function getEventDispatcher(obj) {
- if (!obj._eventDispatcher) {
- obj._eventDispatcher = new EventDispatcher({
- scope: obj,
- toggleEvent: function(name, state) {
- if (state && EventDispatcher.isNative(name)) {
- if (!obj._nativeEvents) {
- obj._nativeEvents = {};
- }
- obj._nativeEvents[name] = true;
- if (obj.state.get('rendered')) {
- bindPendingEvents(obj);
- }
- }
- }
- });
- }
- return obj._eventDispatcher;
- }
- function bindPendingEvents(eventCtrl) {
- var i, l, parents, eventRootCtrl, nativeEvents, name;
- function delegate(e) {
- var control = eventCtrl.getParentCtrl(e.target);
- if (control) {
- control.fire(e.type, e);
- }
- }
- function mouseLeaveHandler() {
- var ctrl = eventRootCtrl._lastHoverCtrl;
- if (ctrl) {
- ctrl.fire("mouseleave", {target: ctrl.getEl()});
- ctrl.parents().each(function(ctrl) {
- ctrl.fire("mouseleave", {target: ctrl.getEl()});
- });
- eventRootCtrl._lastHoverCtrl = null;
- }
- }
- function mouseEnterHandler(e) {
- var ctrl = eventCtrl.getParentCtrl(e.target), lastCtrl = eventRootCtrl._lastHoverCtrl, idx = 0, i, parents, lastParents;
- // Over on a new control
- if (ctrl !== lastCtrl) {
- eventRootCtrl._lastHoverCtrl = ctrl;
- parents = ctrl.parents().toArray().reverse();
- parents.push(ctrl);
- if (lastCtrl) {
- lastParents = lastCtrl.parents().toArray().reverse();
- lastParents.push(lastCtrl);
- for (idx = 0; idx < lastParents.length; idx++) {
- if (parents[idx] !== lastParents[idx]) {
- break;
- }
- }
- for (i = lastParents.length - 1; i >= idx; i--) {
- lastCtrl = lastParents[i];
- lastCtrl.fire("mouseleave", {
- target: lastCtrl.getEl()
- });
- }
- }
- for (i = idx; i < parents.length; i++) {
- ctrl = parents[i];
- ctrl.fire("mouseenter", {
- target: ctrl.getEl()
- });
- }
- }
- }
- function fixWheelEvent(e) {
- e.preventDefault();
- if (e.type == "mousewheel") {
- e.deltaY = -1 / 40 * e.wheelDelta;
- if (e.wheelDeltaX) {
- e.deltaX = -1 / 40 * e.wheelDeltaX;
- }
- } else {
- e.deltaX = 0;
- e.deltaY = e.detail;
- }
- e = eventCtrl.fire("wheel", e);
- }
- nativeEvents = eventCtrl._nativeEvents;
- if (nativeEvents) {
- // Find event root element if it exists
- parents = eventCtrl.parents().toArray();
- parents.unshift(eventCtrl);
- for (i = 0, l = parents.length; !eventRootCtrl && i < l; i++) {
- eventRootCtrl = parents[i]._eventsRoot;
- }
- // Event root wasn't found the use the root control
- if (!eventRootCtrl) {
- eventRootCtrl = parents[parents.length - 1] || eventCtrl;
- }
- // Set the eventsRoot property on children that didn't have it
- eventCtrl._eventsRoot = eventRootCtrl;
- for (l = i, i = 0; i < l; i++) {
- parents[i]._eventsRoot = eventRootCtrl;
- }
- var eventRootDelegates = eventRootCtrl._delegates;
- if (!eventRootDelegates) {
- eventRootDelegates = eventRootCtrl._delegates = {};
- }
- // Bind native event delegates
- for (name in nativeEvents) {
- if (!nativeEvents) {
- return false;
- }
- if (name === "wheel" && !hasWheelEventSupport) {
- if (hasMouseWheelEventSupport) {
- $(eventCtrl.getEl()).on("mousewheel", fixWheelEvent);
- } else {
- $(eventCtrl.getEl()).on("DOMMouseScroll", fixWheelEvent);
- }
- continue;
- }
- // Special treatment for mousenter/mouseleave since these doesn't bubble
- if (name === "mouseenter" || name === "mouseleave") {
- // Fake mousenter/mouseleave
- if (!eventRootCtrl._hasMouseEnter) {
- $(eventRootCtrl.getEl()).on("mouseleave", mouseLeaveHandler).on("mouseover", mouseEnterHandler);
- eventRootCtrl._hasMouseEnter = 1;
- }
- } else if (!eventRootDelegates[name]) {
- $(eventRootCtrl.getEl()).on(name, delegate);
- eventRootDelegates[name] = true;
- }
- // Remove the event once it's bound
- nativeEvents[name] = false;
- }
- }
- }
- return Control;
- });
- // Included from: js/tinymce/classes/ui/Factory.js
- /**
- * Factory.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*global tinymce:true */
- /**
- * This class is a factory for control instances. This enables you
- * to create instances of controls without having to require the UI controls directly.
- *
- * It also allow you to override or add new control types.
- *
- * @class tinymce.ui.Factory
- */
- define("tinymce/ui/Factory", [], function() {
- "use strict";
- var types = {}, namespaceInit;
- return {
- /**
- * Adds a new control instance type to the factory.
- *
- * @method add
- * @param {String} type Type name for example "button".
- * @param {function} typeClass Class type function.
- */
- add: function(type, typeClass) {
- types[type.toLowerCase()] = typeClass;
- },
- /**
- * Returns true/false if the specified type exists or not.
- *
- * @method has
- * @param {String} type Type to look for.
- * @return {Boolean} true/false if the control by name exists.
- */
- has: function(type) {
- return !!types[type.toLowerCase()];
- },
- /**
- * Creates a new control instance based on the settings provided. The instance created will be
- * based on the specified type property it can also create whole structures of components out of
- * the specified JSON object.
- *
- * @example
- * tinymce.ui.Factory.create({
- * type: 'button',
- * text: 'Hello world!'
- * });
- *
- * @method create
- * @param {Object/String} settings Name/Value object with items used to create the type.
- * @return {tinymce.ui.Control} Control instance based on the specified type.
- */
- create: function(type, settings) {
- var ControlType, name, namespace;
- // Build type lookup
- if (!namespaceInit) {
- namespace = tinymce.ui;
- for (name in namespace) {
- types[name.toLowerCase()] = namespace[name];
- }
- namespaceInit = true;
- }
- // If string is specified then use it as the type
- if (typeof type == 'string') {
- settings = settings || {};
- settings.type = type;
- } else {
- settings = type;
- type = settings.type;
- }
- // Find control type
- type = type.toLowerCase();
- ControlType = types[type];
- // #if debug
- if (!ControlType) {
- throw new Error("Could not find control by type: " + type);
- }
- // #endif
- ControlType = new ControlType(settings);
- ControlType.type = type; // Set the type on the instance, this will be used by the Selector engine
- return ControlType;
- }
- };
- });
- // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
- /**
- * KeyboardNavigation.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles keyboard navigation of controls and elements.
- *
- * @class tinymce.ui.KeyboardNavigation
- */
- define("tinymce/ui/KeyboardNavigation", [
- ], function() {
- "use strict";
- /**
- * This class handles all keyboard navigation for WAI-ARIA support. Each root container
- * gets an instance of this class.
- *
- * @constructor
- */
- return function(settings) {
- var root = settings.root, focusedElement, focusedControl;
- function isElement(node) {
- return node && node.nodeType === 1;
- }
- try {
- focusedElement = document.activeElement;
- } catch (ex) {
- // IE sometimes fails to return a proper element
- focusedElement = document.body;
- }
- focusedControl = root.getParentCtrl(focusedElement);
- /**
- * Returns the currently focused elements wai aria role of the currently
- * focused element or specified element.
- *
- * @private
- * @param {Element} elm Optional element to get role from.
- * @return {String} Role of specified element.
- */
- function getRole(elm) {
- elm = elm || focusedElement;
- if (isElement(elm)) {
- return elm.getAttribute('role');
- }
- return null;
- }
- /**
- * Returns the wai role of the parent element of the currently
- * focused element or specified element.
- *
- * @private
- * @param {Element} elm Optional element to get parent role from.
- * @return {String} Role of the first parent that has a role.
- */
- function getParentRole(elm) {
- var role, parent = elm || focusedElement;
- while ((parent = parent.parentNode)) {
- if ((role = getRole(parent))) {
- return role;
- }
- }
- }
- /**
- * Returns a wai aria property by name for example aria-selected.
- *
- * @private
- * @param {String} name Name of the aria property to get for example "disabled".
- * @return {String} Aria property value.
- */
- function getAriaProp(name) {
- var elm = focusedElement;
- if (isElement(elm)) {
- return elm.getAttribute('aria-' + name);
- }
- }
- /**
- * Is the element a text input element or not.
- *
- * @private
- * @param {Element} elm Element to check if it's an text input element or not.
- * @return {Boolean} True/false if the element is a text element or not.
- */
- function isTextInputElement(elm) {
- var tagName = elm.tagName.toUpperCase();
- // Notice: since type can be "email" etc we don't check the type
- // So all input elements gets treated as text input elements
- return tagName == "INPUT" || tagName == "TEXTAREA" || tagName == "SELECT";
- }
- /**
- * Returns true/false if the specified element can be focused or not.
- *
- * @private
- * @param {Element} elm DOM element to check if it can be focused or not.
- * @return {Boolean} True/false if the element can have focus.
- */
- function canFocus(elm) {
- if (isTextInputElement(elm) && !elm.hidden) {
- return true;
- }
- if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell|slider)$/.test(getRole(elm))) {
- return true;
- }
- return false;
- }
- /**
- * Returns an array of focusable visible elements within the specified container element.
- *
- * @private
- * @param {Element} elm DOM element to find focusable elements within.
- * @return {Array} Array of focusable elements.
- */
- function getFocusElements(elm) {
- var elements = [];
- function collect(elm) {
- if (elm.nodeType != 1 || elm.style.display == 'none') {
- return;
- }
- if (canFocus(elm)) {
- elements.push(elm);
- }
- for (var i = 0; i < elm.childNodes.length; i++) {
- collect(elm.childNodes[i]);
- }
- }
- collect(elm || root.getEl());
- return elements;
- }
- /**
- * Returns the navigation root control for the specified control. The navigation root
- * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
- * It will look for parents of the specified target control or the currently focused control if this option is omitted.
- *
- * @private
- * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
- * @return {tinymce.ui.Control} Navigation root control.
- */
- function getNavigationRoot(targetControl) {
- var navigationRoot, controls;
- targetControl = targetControl || focusedControl;
- controls = targetControl.parents().toArray();
- controls.unshift(targetControl);
- for (var i = 0; i < controls.length; i++) {
- navigationRoot = controls[i];
- if (navigationRoot.settings.ariaRoot) {
- break;
- }
- }
- return navigationRoot;
- }
- /**
- * Focuses the first item in the specified targetControl element or the last aria index if the
- * navigation root has the ariaRemember option enabled.
- *
- * @private
- * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
- */
- function focusFirst(targetControl) {
- var navigationRoot = getNavigationRoot(targetControl);
- var focusElements = getFocusElements(navigationRoot.getEl());
- if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
- moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
- } else {
- moveFocusToIndex(0, focusElements);
- }
- }
- /**
- * Moves the focus to the specified index within the elements list.
- * This will scope the index to the size of the element list if it changed.
- *
- * @private
- * @param {Number} idx Specified index to move to.
- * @param {Array} elements Array with dom elements to move focus within.
- * @return {Number} Input index or a changed index if it was out of range.
- */
- function moveFocusToIndex(idx, elements) {
- if (idx < 0) {
- idx = elements.length - 1;
- } else if (idx >= elements.length) {
- idx = 0;
- }
- if (elements[idx]) {
- elements[idx].focus();
- }
- return idx;
- }
- /**
- * Moves the focus forwards or backwards.
- *
- * @private
- * @param {Number} dir Direction to move in positive means forward, negative means backwards.
- * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
- */
- function moveFocus(dir, elements) {
- var idx = -1, navigationRoot = getNavigationRoot();
- elements = elements || getFocusElements(navigationRoot.getEl());
- for (var i = 0; i < elements.length; i++) {
- if (elements[i] === focusedElement) {
- idx = i;
- }
- }
- idx += dir;
- navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
- }
- /**
- * Moves the focus to the left this is called by the left key.
- *
- * @private
- */
- function left() {
- var parentRole = getParentRole();
- if (parentRole == "tablist") {
- moveFocus(-1, getFocusElements(focusedElement.parentNode));
- } else if (focusedControl.parent().submenu) {
- cancel();
- } else {
- moveFocus(-1);
- }
- }
- /**
- * Moves the focus to the right this is called by the right key.
- *
- * @private
- */
- function right() {
- var role = getRole(), parentRole = getParentRole();
- if (parentRole == "tablist") {
- moveFocus(1, getFocusElements(focusedElement.parentNode));
- } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
- enter();
- } else {
- moveFocus(1);
- }
- }
- /**
- * Moves the focus to the up this is called by the up key.
- *
- * @private
- */
- function up() {
- moveFocus(-1);
- }
- /**
- * Moves the focus to the up this is called by the down key.
- *
- * @private
- */
- function down() {
- var role = getRole(), parentRole = getParentRole();
- if (role == "menuitem" && parentRole == "menubar") {
- enter();
- } else if (role == "button" && getAriaProp('haspopup')) {
- enter({key: 'down'});
- } else {
- moveFocus(1);
- }
- }
- /**
- * Moves the focus to the next item or previous item depending on shift key.
- *
- * @private
- * @param {DOMEvent} e DOM event object.
- */
- function tab(e) {
- var parentRole = getParentRole();
- if (parentRole == "tablist") {
- var elm = getFocusElements(focusedControl.getEl('body'))[0];
- if (elm) {
- elm.focus();
- }
- } else {
- moveFocus(e.shiftKey ? -1 : 1);
- }
- }
- /**
- * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
- *
- * @private
- */
- function cancel() {
- focusedControl.fire('cancel');
- }
- /**
- * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
- *
- * @private
- * @param {Object} aria Optional aria data to pass along with the enter event.
- */
- function enter(aria) {
- aria = aria || {};
- focusedControl.fire('click', {target: focusedElement, aria: aria});
- }
- root.on('keydown', function(e) {
- function handleNonTabOrEscEvent(e, handler) {
- // Ignore non tab keys for text elements
- if (isTextInputElement(focusedElement)) {
- return;
- }
- if (getRole(focusedElement) === 'slider') {
- return;
- }
- if (handler(e) !== false) {
- e.preventDefault();
- }
- }
- if (e.isDefaultPrevented()) {
- return;
- }
- switch (e.keyCode) {
- case 37: // DOM_VK_LEFT
- handleNonTabOrEscEvent(e, left);
- break;
- case 39: // DOM_VK_RIGHT
- handleNonTabOrEscEvent(e, right);
- break;
- case 38: // DOM_VK_UP
- handleNonTabOrEscEvent(e, up);
- break;
- case 40: // DOM_VK_DOWN
- handleNonTabOrEscEvent(e, down);
- break;
- case 27: // DOM_VK_ESCAPE
- cancel();
- break;
- case 14: // DOM_VK_ENTER
- case 13: // DOM_VK_RETURN
- case 32: // DOM_VK_SPACE
- handleNonTabOrEscEvent(e, enter);
- break;
- case 9: // DOM_VK_TAB
- if (tab(e) !== false) {
- e.preventDefault();
- }
- break;
- }
- });
- root.on('focusin', function(e) {
- focusedElement = e.target;
- focusedControl = e.control;
- });
- return {
- focusFirst: focusFirst
- };
- };
- });
- // Included from: js/tinymce/classes/ui/Container.js
- /**
- * Container.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Container control. This is extended by all controls that can have
- * children such as panels etc. You can also use this class directly as an
- * generic container instance. The container doesn't have any specific role or style.
- *
- * @-x-less Container.less
- * @class tinymce.ui.Container
- * @extends tinymce.ui.Control
- */
- define("tinymce/ui/Container", [
- "tinymce/ui/Control",
- "tinymce/ui/Collection",
- "tinymce/ui/Selector",
- "tinymce/ui/Factory",
- "tinymce/ui/KeyboardNavigation",
- "tinymce/util/Tools",
- "tinymce/dom/DomQuery",
- "tinymce/ui/ClassList",
- "tinymce/ui/ReflowQueue"
- ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, $, ClassList, ReflowQueue) {
- "use strict";
- var selectorCache = {};
- return Control.extend({
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Array} items Items to add to container in JSON format or control instances.
- * @setting {String} layout Layout manager by name to use.
- * @setting {Object} defaults Default settings to apply to all items.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- settings = self.settings;
- if (settings.fixed) {
- self.state.set('fixed', true);
- }
- self._items = new Collection();
- if (self.isRtl()) {
- self.classes.add('rtl');
- }
- self.bodyClasses = new ClassList(function() {
- if (self.state.get('rendered')) {
- self.getEl('body').className = this.toString();
- }
- });
- self.bodyClasses.prefix = self.classPrefix;
- self.classes.add('container');
- self.bodyClasses.add('container-body');
- if (settings.containerCls) {
- self.classes.add(settings.containerCls);
- }
- self._layout = Factory.create((settings.layout || '') + 'layout');
- if (self.settings.items) {
- self.add(self.settings.items);
- } else {
- self.add(self.render());
- }
- // TODO: Fix this!
- self._hasBody = true;
- },
- /**
- * Returns a collection of child items that the container currently have.
- *
- * @method items
- * @return {tinymce.ui.Collection} Control collection direct child controls.
- */
- items: function() {
- return this._items;
- },
- /**
- * Find child controls by selector.
- *
- * @method find
- * @param {String} selector Selector CSS pattern to find children by.
- * @return {tinymce.ui.Collection} Control collection with child controls.
- */
- find: function(selector) {
- selector = selectorCache[selector] = selectorCache[selector] || new Selector(selector);
- return selector.find(this);
- },
- /**
- * Adds one or many items to the current container. This will create instances of
- * the object representations if needed.
- *
- * @method add
- * @param {Array/Object/tinymce.ui.Control} items Array or item that will be added to the container.
- * @return {tinymce.ui.Collection} Current collection control.
- */
- add: function(items) {
- var self = this;
- self.items().add(self.create(items)).parent(self);
- return self;
- },
- /**
- * Focuses the current container instance. This will look
- * for the first control in the container and focus that.
- *
- * @method focus
- * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
- * @return {tinymce.ui.Collection} Current instance.
- */
- focus: function(keyboard) {
- var self = this, focusCtrl, keyboardNav, items;
- if (keyboard) {
- keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
- if (keyboardNav) {
- keyboardNav.focusFirst(self);
- return;
- }
- }
- items = self.find('*');
- // TODO: Figure out a better way to auto focus alert dialog buttons
- if (self.statusbar) {
- items.add(self.statusbar.items());
- }
- items.each(function(ctrl) {
- if (ctrl.settings.autofocus) {
- focusCtrl = null;
- return false;
- }
- if (ctrl.canFocus) {
- focusCtrl = focusCtrl || ctrl;
- }
- });
- if (focusCtrl) {
- focusCtrl.focus();
- }
- return self;
- },
- /**
- * Replaces the specified child control with a new control.
- *
- * @method replace
- * @param {tinymce.ui.Control} oldItem Old item to be replaced.
- * @param {tinymce.ui.Control} newItem New item to be inserted.
- */
- replace: function(oldItem, newItem) {
- var ctrlElm, items = this.items(), i = items.length;
- // Replace the item in collection
- while (i--) {
- if (items[i] === oldItem) {
- items[i] = newItem;
- break;
- }
- }
- if (i >= 0) {
- // Remove new item from DOM
- ctrlElm = newItem.getEl();
- if (ctrlElm) {
- ctrlElm.parentNode.removeChild(ctrlElm);
- }
- // Remove old item from DOM
- ctrlElm = oldItem.getEl();
- if (ctrlElm) {
- ctrlElm.parentNode.removeChild(ctrlElm);
- }
- }
- // Adopt the item
- newItem.parent(this);
- },
- /**
- * Creates the specified items. If any of the items is plain JSON style objects
- * it will convert these into real tinymce.ui.Control instances.
- *
- * @method create
- * @param {Array} items Array of items to convert into control instances.
- * @return {Array} Array with control instances.
- */
- create: function(items) {
- var self = this, settings, ctrlItems = [];
- // Non array structure, then force it into an array
- if (!Tools.isArray(items)) {
- items = [items];
- }
- // Add default type to each child control
- Tools.each(items, function(item) {
- if (item) {
- // Construct item if needed
- if (!(item instanceof Control)) {
- // Name only then convert it to an object
- if (typeof item == "string") {
- item = {type: item};
- }
- // Create control instance based on input settings and default settings
- settings = Tools.extend({}, self.settings.defaults, item);
- item.type = settings.type = settings.type || item.type || self.settings.defaultType ||
- (settings.defaults ? settings.defaults.type : null);
- item = Factory.create(settings);
- }
- ctrlItems.push(item);
- }
- });
- return ctrlItems;
- },
- /**
- * Renders new control instances.
- *
- * @private
- */
- renderNew: function() {
- var self = this;
- // Render any new items
- self.items().each(function(ctrl, index) {
- var containerElm;
- ctrl.parent(self);
- if (!ctrl.state.get('rendered')) {
- containerElm = self.getEl('body');
- // Insert or append the item
- if (containerElm.hasChildNodes() && index <= containerElm.childNodes.length - 1) {
- $(containerElm.childNodes[index]).before(ctrl.renderHtml());
- } else {
- $(containerElm).append(ctrl.renderHtml());
- }
- ctrl.postRender();
- ReflowQueue.add(ctrl);
- }
- });
- self._layout.applyClasses(self.items().filter(':visible'));
- self._lastRect = null;
- return self;
- },
- /**
- * Appends new instances to the current container.
- *
- * @method append
- * @param {Array/tinymce.ui.Collection} items Array if controls to append.
- * @return {tinymce.ui.Container} Current container instance.
- */
- append: function(items) {
- return this.add(items).renderNew();
- },
- /**
- * Prepends new instances to the current container.
- *
- * @method prepend
- * @param {Array/tinymce.ui.Collection} items Array if controls to prepend.
- * @return {tinymce.ui.Container} Current container instance.
- */
- prepend: function(items) {
- var self = this;
- self.items().set(self.create(items).concat(self.items().toArray()));
- return self.renderNew();
- },
- /**
- * Inserts an control at a specific index.
- *
- * @method insert
- * @param {Array/tinymce.ui.Collection} items Array if controls to insert.
- * @param {Number} index Index to insert controls at.
- * @param {Boolean} [before=false] Inserts controls before the index.
- */
- insert: function(items, index, before) {
- var self = this, curItems, beforeItems, afterItems;
- items = self.create(items);
- curItems = self.items();
- if (!before && index < curItems.length - 1) {
- index += 1;
- }
- if (index >= 0 && index < curItems.length) {
- beforeItems = curItems.slice(0, index).toArray();
- afterItems = curItems.slice(index).toArray();
- curItems.set(beforeItems.concat(items, afterItems));
- }
- return self.renderNew();
- },
- /**
- * Populates the form fields from the specified JSON data object.
- *
- * Control items in the form that matches the data will have it's value set.
- *
- * @method fromJSON
- * @param {Object} data JSON data object to set control values by.
- * @return {tinymce.ui.Container} Current form instance.
- */
- fromJSON: function(data) {
- var self = this;
- for (var name in data) {
- self.find('#' + name).value(data[name]);
- }
- return self;
- },
- /**
- * Serializes the form into a JSON object by getting all items
- * that has a name and a value.
- *
- * @method toJSON
- * @return {Object} JSON object with form data.
- */
- toJSON: function() {
- var self = this, data = {};
- self.find('*').each(function(ctrl) {
- var name = ctrl.name(), value = ctrl.value();
- if (name && typeof value != "undefined") {
- data[name] = value;
- }
- });
- return data;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout, role = this.settings.role;
- self.preRender();
- layout.preRender(self);
- return (
- '<div id="' + self._id + '" class="' + self.classes + '"' + (role ? ' role="' + this.settings.role + '"' : '') + '>' +
- '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
- (self.settings.html || '') + layout.renderHtml(self) +
- '</div>' +
- '</div>'
- );
- },
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.Container} Current combobox instance.
- */
- postRender: function() {
- var self = this, box;
- self.items().exec('postRender');
- self._super();
- self._layout.postRender(self);
- self.state.set('rendered', true);
- if (self.settings.style) {
- self.$el.css(self.settings.style);
- }
- if (self.settings.border) {
- box = self.borderBox;
- self.$el.css({
- 'border-top-width': box.top,
- 'border-right-width': box.right,
- 'border-bottom-width': box.bottom,
- 'border-left-width': box.left
- });
- }
- if (!self.parent()) {
- self.keyboardNav = new KeyboardNavigation({
- root: self
- });
- }
- return self;
- },
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, layoutRect = self._super();
- // Recalc container size by asking layout manager
- self._layout.recalc(self);
- return layoutRect;
- },
- /**
- * Recalculates the positions of the controls in the current container.
- * This is invoked by the reflow method and shouldn't be called directly.
- *
- * @method recalc
- */
- recalc: function() {
- var self = this, rect = self._layoutRect, lastRect = self._lastRect;
- if (!lastRect || lastRect.w != rect.w || lastRect.h != rect.h) {
- self._layout.recalc(self);
- rect = self.layoutRect();
- self._lastRect = {x: rect.x, y: rect.y, w: rect.w, h: rect.h};
- return true;
- }
- },
- /**
- * Reflows the current container and it's children and possible parents.
- * This should be used after you for example append children to the current control so
- * that the layout managers know that they need to reposition everything.
- *
- * @example
- * container.append({type: 'button', text: 'My button'}).reflow();
- *
- * @method reflow
- * @return {tinymce.ui.Container} Current container instance.
- */
- reflow: function() {
- var i;
- ReflowQueue.remove(this);
- if (this.visible()) {
- Control.repaintControls = [];
- Control.repaintControls.map = {};
- this.recalc();
- i = Control.repaintControls.length;
- while (i--) {
- Control.repaintControls[i].repaint();
- }
- // TODO: Fix me!
- if (this.settings.layout !== "flow" && this.settings.layout !== "stack") {
- this.repaint();
- }
- Control.repaintControls = [];
- }
- return this;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/DragHelper.js
- /**
- * DragHelper.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Drag/drop helper class.
- *
- * @example
- * var dragHelper = new tinymce.ui.DragHelper('mydiv', {
- * start: function(evt) {
- * },
- *
- * drag: function(evt) {
- * },
- *
- * end: function(evt) {
- * }
- * });
- *
- * @class tinymce.ui.DragHelper
- */
- define("tinymce/ui/DragHelper", [
- "tinymce/dom/DomQuery"
- ], function($) {
- "use strict";
- function getDocumentSize(doc) {
- var documentElement, body, scrollWidth, clientWidth;
- var offsetWidth, scrollHeight, clientHeight, offsetHeight, max = Math.max;
- documentElement = doc.documentElement;
- body = doc.body;
- scrollWidth = max(documentElement.scrollWidth, body.scrollWidth);
- clientWidth = max(documentElement.clientWidth, body.clientWidth);
- offsetWidth = max(documentElement.offsetWidth, body.offsetWidth);
- scrollHeight = max(documentElement.scrollHeight, body.scrollHeight);
- clientHeight = max(documentElement.clientHeight, body.clientHeight);
- offsetHeight = max(documentElement.offsetHeight, body.offsetHeight);
- return {
- width: scrollWidth < offsetWidth ? clientWidth : scrollWidth,
- height: scrollHeight < offsetHeight ? clientHeight : scrollHeight
- };
- }
- function updateWithTouchData(e) {
- var keys, i;
- if (e.changedTouches) {
- keys = "screenX screenY pageX pageY clientX clientY".split(' ');
- for (i = 0; i < keys.length; i++) {
- e[keys[i]] = e.changedTouches[0][keys[i]];
- }
- }
- }
- return function(id, settings) {
- var $eventOverlay, doc = settings.document || document, downButton, start, stop, drag, startX, startY;
- settings = settings || {};
- function getHandleElm() {
- return doc.getElementById(settings.handle || id);
- }
- start = function(e) {
- var docSize = getDocumentSize(doc), handleElm, cursor;
- updateWithTouchData(e);
- e.preventDefault();
- downButton = e.button;
- handleElm = getHandleElm();
- startX = e.screenX;
- startY = e.screenY;
- // Grab cursor from handle so we can place it on overlay
- if (window.getComputedStyle) {
- cursor = window.getComputedStyle(handleElm, null).getPropertyValue("cursor");
- } else {
- cursor = handleElm.runtimeStyle.cursor;
- }
- $eventOverlay = $('<div>').css({
- position: "absolute",
- top: 0, left: 0,
- width: docSize.width,
- height: docSize.height,
- zIndex: 0x7FFFFFFF,
- opacity: 0.0001,
- cursor: cursor
- }).appendTo(doc.body);
- $(doc).on('mousemove touchmove', drag).on('mouseup touchend', stop);
- settings.start(e);
- };
- drag = function(e) {
- updateWithTouchData(e);
- if (e.button !== downButton) {
- return stop(e);
- }
- e.deltaX = e.screenX - startX;
- e.deltaY = e.screenY - startY;
- e.preventDefault();
- settings.drag(e);
- };
- stop = function(e) {
- updateWithTouchData(e);
- $(doc).off('mousemove touchmove', drag).off('mouseup touchend', stop);
- $eventOverlay.remove();
- if (settings.stop) {
- settings.stop(e);
- }
- };
- /**
- * Destroys the drag/drop helper instance.
- *
- * @method destroy
- */
- this.destroy = function() {
- $(getHandleElm()).off();
- };
- $(getHandleElm()).on('mousedown touchstart', start);
- };
- });
- // Included from: js/tinymce/classes/ui/Scrollable.js
- /**
- * Scrollable.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This mixin makes controls scrollable using custom scrollbars.
- *
- * @-x-less Scrollable.less
- * @mixin tinymce.ui.Scrollable
- */
- define("tinymce/ui/Scrollable", [
- "tinymce/dom/DomQuery",
- "tinymce/ui/DragHelper"
- ], function($, DragHelper) {
- "use strict";
- return {
- init: function() {
- var self = this;
- self.on('repaint', self.renderScroll);
- },
- renderScroll: function() {
- var self = this, margin = 2;
- function repaintScroll() {
- var hasScrollH, hasScrollV, bodyElm;
- function repaintAxis(axisName, posName, sizeName, contentSizeName, hasScroll, ax) {
- var containerElm, scrollBarElm, scrollThumbElm;
- var containerSize, scrollSize, ratio, rect;
- var posNameLower, sizeNameLower;
- scrollBarElm = self.getEl('scroll' + axisName);
- if (scrollBarElm) {
- posNameLower = posName.toLowerCase();
- sizeNameLower = sizeName.toLowerCase();
- $(self.getEl('absend')).css(posNameLower, self.layoutRect()[contentSizeName] - 1);
- if (!hasScroll) {
- $(scrollBarElm).css('display', 'none');
- return;
- }
- $(scrollBarElm).css('display', 'block');
- containerElm = self.getEl('body');
- scrollThumbElm = self.getEl('scroll' + axisName + "t");
- containerSize = containerElm["client" + sizeName] - (margin * 2);
- containerSize -= hasScrollH && hasScrollV ? scrollBarElm["client" + ax] : 0;
- scrollSize = containerElm["scroll" + sizeName];
- ratio = containerSize / scrollSize;
- rect = {};
- rect[posNameLower] = containerElm["offset" + posName] + margin;
- rect[sizeNameLower] = containerSize;
- $(scrollBarElm).css(rect);
- rect = {};
- rect[posNameLower] = containerElm["scroll" + posName] * ratio;
- rect[sizeNameLower] = containerSize * ratio;
- $(scrollThumbElm).css(rect);
- }
- }
- bodyElm = self.getEl('body');
- hasScrollH = bodyElm.scrollWidth > bodyElm.clientWidth;
- hasScrollV = bodyElm.scrollHeight > bodyElm.clientHeight;
- repaintAxis("h", "Left", "Width", "contentW", hasScrollH, "Height");
- repaintAxis("v", "Top", "Height", "contentH", hasScrollV, "Width");
- }
- function addScroll() {
- function addScrollAxis(axisName, posName, sizeName, deltaPosName, ax) {
- var scrollStart, axisId = self._id + '-scroll' + axisName, prefix = self.classPrefix;
- $(self.getEl()).append(
- '<div id="' + axisId + '" class="' + prefix + 'scrollbar ' + prefix + 'scrollbar-' + axisName + '">' +
- '<div id="' + axisId + 't" class="' + prefix + 'scrollbar-thumb"></div>' +
- '</div>'
- );
- self.draghelper = new DragHelper(axisId + 't', {
- start: function() {
- scrollStart = self.getEl('body')["scroll" + posName];
- $('#' + axisId).addClass(prefix + 'active');
- },
- drag: function(e) {
- var ratio, hasScrollH, hasScrollV, containerSize, layoutRect = self.layoutRect();
- hasScrollH = layoutRect.contentW > layoutRect.innerW;
- hasScrollV = layoutRect.contentH > layoutRect.innerH;
- containerSize = self.getEl('body')["client" + sizeName] - (margin * 2);
- containerSize -= hasScrollH && hasScrollV ? self.getEl('scroll' + axisName)["client" + ax] : 0;
- ratio = containerSize / self.getEl('body')["scroll" + sizeName];
- self.getEl('body')["scroll" + posName] = scrollStart + (e["delta" + deltaPosName] / ratio);
- },
- stop: function() {
- $('#' + axisId).removeClass(prefix + 'active');
- }
- });
- }
- self.classes.add('scroll');
- addScrollAxis("v", "Top", "Height", "Y", "Width");
- addScrollAxis("h", "Left", "Width", "X", "Height");
- }
- if (self.settings.autoScroll) {
- if (!self._hasScroll) {
- self._hasScroll = true;
- addScroll();
- self.on('wheel', function(e) {
- var bodyEl = self.getEl('body');
- bodyEl.scrollLeft += (e.deltaX || 0) * 10;
- bodyEl.scrollTop += e.deltaY * 10;
- repaintScroll();
- });
- $(self.getEl('body')).on("scroll", repaintScroll);
- }
- repaintScroll();
- }
- }
- };
- });
- // Included from: js/tinymce/classes/ui/Panel.js
- /**
- * Panel.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new panel.
- *
- * @-x-less Panel.less
- * @class tinymce.ui.Panel
- * @extends tinymce.ui.Container
- * @mixes tinymce.ui.Scrollable
- */
- define("tinymce/ui/Panel", [
- "tinymce/ui/Container",
- "tinymce/ui/Scrollable"
- ], function(Container, Scrollable) {
- "use strict";
- return Container.extend({
- Defaults: {
- layout: 'fit',
- containerCls: 'panel'
- },
- Mixins: [Scrollable],
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout, innerHtml = self.settings.html;
- self.preRender();
- layout.preRender(self);
- if (typeof innerHtml == "undefined") {
- innerHtml = (
- '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
- layout.renderHtml(self) +
- '</div>'
- );
- } else {
- if (typeof innerHtml == 'function') {
- innerHtml = innerHtml.call(self);
- }
- self._hasBody = false;
- }
- return (
- '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1" role="group">' +
- (self._preBodyHtml || '') +
- innerHtml +
- '</div>'
- );
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Movable.js
- /**
- * Movable.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Movable mixin. Makes controls movable absolute and relative to other elements.
- *
- * @mixin tinymce.ui.Movable
- */
- define("tinymce/ui/Movable", [
- "tinymce/ui/DomUtils"
- ], function(DomUtils) {
- "use strict";
- function calculateRelativePosition(ctrl, targetElm, rel) {
- var ctrlElm, pos, x, y, selfW, selfH, targetW, targetH, viewport, size;
- viewport = DomUtils.getViewPort();
- // Get pos of target
- pos = DomUtils.getPos(targetElm);
- x = pos.x;
- y = pos.y;
- if (ctrl.state.get('fixed') && DomUtils.getRuntimeStyle(document.body, 'position') == 'static') {
- x -= viewport.x;
- y -= viewport.y;
- }
- // Get size of self
- ctrlElm = ctrl.getEl();
- size = DomUtils.getSize(ctrlElm);
- selfW = size.width;
- selfH = size.height;
- // Get size of target
- size = DomUtils.getSize(targetElm);
- targetW = size.width;
- targetH = size.height;
- // Parse align string
- rel = (rel || '').split('');
- // Target corners
- if (rel[0] === 'b') {
- y += targetH;
- }
- if (rel[1] === 'r') {
- x += targetW;
- }
- if (rel[0] === 'c') {
- y += Math.round(targetH / 2);
- }
- if (rel[1] === 'c') {
- x += Math.round(targetW / 2);
- }
- // Self corners
- if (rel[3] === 'b') {
- y -= selfH;
- }
- if (rel[4] === 'r') {
- x -= selfW;
- }
- if (rel[3] === 'c') {
- y -= Math.round(selfH / 2);
- }
- if (rel[4] === 'c') {
- x -= Math.round(selfW / 2);
- }
- return {
- x: x,
- y: y,
- w: selfW,
- h: selfH
- };
- }
- return {
- /**
- * Tests various positions to get the most suitable one.
- *
- * @method testMoveRel
- * @param {DOMElement} elm Element to position against.
- * @param {Array} rels Array with relative positions.
- * @return {String} Best suitable relative position.
- */
- testMoveRel: function(elm, rels) {
- var viewPortRect = DomUtils.getViewPort();
- for (var i = 0; i < rels.length; i++) {
- var pos = calculateRelativePosition(this, elm, rels[i]);
- if (this.state.get('fixed')) {
- if (pos.x > 0 && pos.x + pos.w < viewPortRect.w && pos.y > 0 && pos.y + pos.h < viewPortRect.h) {
- return rels[i];
- }
- } else {
- if (pos.x > viewPortRect.x && pos.x + pos.w < viewPortRect.w + viewPortRect.x &&
- pos.y > viewPortRect.y && pos.y + pos.h < viewPortRect.h + viewPortRect.y) {
- return rels[i];
- }
- }
- }
- return rels[0];
- },
- /**
- * Move relative to the specified element.
- *
- * @method moveRel
- * @param {Element} elm Element to move relative to.
- * @param {String} rel Relative mode. For example: br-tl.
- * @return {tinymce.ui.Control} Current control instance.
- */
- moveRel: function(elm, rel) {
- if (typeof rel != 'string') {
- rel = this.testMoveRel(elm, rel);
- }
- var pos = calculateRelativePosition(this, elm, rel);
- return this.moveTo(pos.x, pos.y);
- },
- /**
- * Move by a relative x, y values.
- *
- * @method moveBy
- * @param {Number} dx Relative x position.
- * @param {Number} dy Relative y position.
- * @return {tinymce.ui.Control} Current control instance.
- */
- moveBy: function(dx, dy) {
- var self = this, rect = self.layoutRect();
- self.moveTo(rect.x + dx, rect.y + dy);
- return self;
- },
- /**
- * Move to absolute position.
- *
- * @method moveTo
- * @param {Number} x Absolute x position.
- * @param {Number} y Absolute y position.
- * @return {tinymce.ui.Control} Current control instance.
- */
- moveTo: function(x, y) {
- var self = this;
- // TODO: Move this to some global class
- function constrain(value, max, size) {
- if (value < 0) {
- return 0;
- }
- if (value + size > max) {
- value = max - size;
- return value < 0 ? 0 : value;
- }
- return value;
- }
- if (self.settings.constrainToViewport) {
- var viewPortRect = DomUtils.getViewPort(window);
- var layoutRect = self.layoutRect();
- x = constrain(x, viewPortRect.w + viewPortRect.x, layoutRect.w);
- y = constrain(y, viewPortRect.h + viewPortRect.y, layoutRect.h);
- }
- if (self.state.get('rendered')) {
- self.layoutRect({x: x, y: y}).repaint();
- } else {
- self.settings.x = x;
- self.settings.y = y;
- }
- self.fire('move', {x: x, y: y});
- return self;
- }
- };
- });
- // Included from: js/tinymce/classes/ui/Resizable.js
- /**
- * Resizable.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Resizable mixin. Enables controls to be resized.
- *
- * @mixin tinymce.ui.Resizable
- */
- define("tinymce/ui/Resizable", [
- "tinymce/ui/DomUtils"
- ], function(DomUtils) {
- "use strict";
- return {
- /**
- * Resizes the control to contents.
- *
- * @method resizeToContent
- */
- resizeToContent: function() {
- this._layoutRect.autoResize = true;
- this._lastRect = null;
- this.reflow();
- },
- /**
- * Resizes the control to a specific width/height.
- *
- * @method resizeTo
- * @param {Number} w Control width.
- * @param {Number} h Control height.
- * @return {tinymce.ui.Control} Current control instance.
- */
- resizeTo: function(w, h) {
- // TODO: Fix hack
- if (w <= 1 || h <= 1) {
- var rect = DomUtils.getWindowSize();
- w = w <= 1 ? w * rect.w : w;
- h = h <= 1 ? h * rect.h : h;
- }
- this._layoutRect.autoResize = false;
- return this.layoutRect({minW: w, minH: h, w: w, h: h}).reflow();
- },
- /**
- * Resizes the control to a specific relative width/height.
- *
- * @method resizeBy
- * @param {Number} dw Relative control width.
- * @param {Number} dh Relative control height.
- * @return {tinymce.ui.Control} Current control instance.
- */
- resizeBy: function(dw, dh) {
- var self = this, rect = self.layoutRect();
- return self.resizeTo(rect.w + dw, rect.h + dh);
- }
- };
- });
- // Included from: js/tinymce/classes/ui/FloatPanel.js
- /**
- * FloatPanel.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class creates a floating panel.
- *
- * @-x-less FloatPanel.less
- * @class tinymce.ui.FloatPanel
- * @extends tinymce.ui.Panel
- * @mixes tinymce.ui.Movable
- * @mixes tinymce.ui.Resizable
- */
- define("tinymce/ui/FloatPanel", [
- "tinymce/ui/Panel",
- "tinymce/ui/Movable",
- "tinymce/ui/Resizable",
- "tinymce/ui/DomUtils",
- "tinymce/dom/DomQuery",
- "tinymce/util/Delay"
- ], function(Panel, Movable, Resizable, DomUtils, $, Delay) {
- "use strict";
- var documentClickHandler, documentScrollHandler, windowResizeHandler, visiblePanels = [];
- var zOrder = [], hasModal;
- function isChildOf(ctrl, parent) {
- while (ctrl) {
- if (ctrl == parent) {
- return true;
- }
- ctrl = ctrl.parent();
- }
- }
- function skipOrHidePanels(e) {
- // Hide any float panel when a click/focus out is out side that float panel and the
- // float panels direct parent for example a click on a menu button
- var i = visiblePanels.length;
- while (i--) {
- var panel = visiblePanels[i], clickCtrl = panel.getParentCtrl(e.target);
- if (panel.settings.autohide) {
- if (clickCtrl) {
- if (isChildOf(clickCtrl, panel) || panel.parent() === clickCtrl) {
- continue;
- }
- }
- e = panel.fire('autohide', {target: e.target});
- if (!e.isDefaultPrevented()) {
- panel.hide();
- }
- }
- }
- }
- function bindDocumentClickHandler() {
- if (!documentClickHandler) {
- documentClickHandler = function(e) {
- // Gecko fires click event and in the wrong order on Mac so lets normalize
- if (e.button == 2) {
- return;
- }
- skipOrHidePanels(e);
- };
- $(document).on('click touchstart', documentClickHandler);
- }
- }
- function bindDocumentScrollHandler() {
- if (!documentScrollHandler) {
- documentScrollHandler = function() {
- var i;
- i = visiblePanels.length;
- while (i--) {
- repositionPanel(visiblePanels[i]);
- }
- };
- $(window).on('scroll', documentScrollHandler);
- }
- }
- function bindWindowResizeHandler() {
- if (!windowResizeHandler) {
- var docElm = document.documentElement, clientWidth = docElm.clientWidth, clientHeight = docElm.clientHeight;
- windowResizeHandler = function() {
- // Workaround for #7065 IE 7 fires resize events event though the window wasn't resized
- if (!document.all || clientWidth != docElm.clientWidth || clientHeight != docElm.clientHeight) {
- clientWidth = docElm.clientWidth;
- clientHeight = docElm.clientHeight;
- FloatPanel.hideAll();
- }
- };
- $(window).on('resize', windowResizeHandler);
- }
- }
- /**
- * Repositions the panel to the top of page if the panel is outside of the visual viewport. It will
- * also reposition all child panels of the current panel.
- */
- function repositionPanel(panel) {
- var scrollY = DomUtils.getViewPort().y;
- function toggleFixedChildPanels(fixed, deltaY) {
- var parent;
- for (var i = 0; i < visiblePanels.length; i++) {
- if (visiblePanels[i] != panel) {
- parent = visiblePanels[i].parent();
- while (parent && (parent = parent.parent())) {
- if (parent == panel) {
- visiblePanels[i].fixed(fixed).moveBy(0, deltaY).repaint();
- }
- }
- }
- }
- }
- if (panel.settings.autofix) {
- if (!panel.state.get('fixed')) {
- panel._autoFixY = panel.layoutRect().y;
- if (panel._autoFixY < scrollY) {
- panel.fixed(true).layoutRect({y: 0}).repaint();
- toggleFixedChildPanels(true, scrollY - panel._autoFixY);
- }
- } else {
- if (panel._autoFixY > scrollY) {
- panel.fixed(false).layoutRect({y: panel._autoFixY}).repaint();
- toggleFixedChildPanels(false, panel._autoFixY - scrollY);
- }
- }
- }
- }
- function addRemove(add, ctrl) {
- var i, zIndex = FloatPanel.zIndex || 0xFFFF, topModal;
- if (add) {
- zOrder.push(ctrl);
- } else {
- i = zOrder.length;
- while (i--) {
- if (zOrder[i] === ctrl) {
- zOrder.splice(i, 1);
- }
- }
- }
- if (zOrder.length) {
- for (i = 0; i < zOrder.length; i++) {
- if (zOrder[i].modal) {
- zIndex++;
- topModal = zOrder[i];
- }
- zOrder[i].getEl().style.zIndex = zIndex;
- zOrder[i].zIndex = zIndex;
- zIndex++;
- }
- }
- var modalBlockEl = $('#' + ctrl.classPrefix + 'modal-block', ctrl.getContainerElm())[0];
- if (topModal) {
- $(modalBlockEl).css('z-index', topModal.zIndex - 1);
- } else if (modalBlockEl) {
- modalBlockEl.parentNode.removeChild(modalBlockEl);
- hasModal = false;
- }
- FloatPanel.currentZIndex = zIndex;
- }
- var FloatPanel = Panel.extend({
- Mixins: [Movable, Resizable],
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Boolean} autohide Automatically hide the panel.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- self._eventsRoot = self;
- self.classes.add('floatpanel');
- // Hide floatpanes on click out side the root button
- if (settings.autohide) {
- bindDocumentClickHandler();
- bindWindowResizeHandler();
- visiblePanels.push(self);
- }
- if (settings.autofix) {
- bindDocumentScrollHandler();
- self.on('move', function() {
- repositionPanel(this);
- });
- }
- self.on('postrender show', function(e) {
- if (e.control == self) {
- var $modalBlockEl, prefix = self.classPrefix;
- if (self.modal && !hasModal) {
- $modalBlockEl = $('#' + prefix + 'modal-block', self.getContainerElm());
- if (!$modalBlockEl[0]) {
- $modalBlockEl = $(
- '<div id="' + prefix + 'modal-block" class="' + prefix + 'reset ' + prefix + 'fade"></div>'
- ).appendTo(self.getContainerElm());
- }
- Delay.setTimeout(function() {
- $modalBlockEl.addClass(prefix + 'in');
- $(self.getEl()).addClass(prefix + 'in');
- });
- hasModal = true;
- }
- addRemove(true, self);
- }
- });
- self.on('show', function() {
- self.parents().each(function(ctrl) {
- if (ctrl.state.get('fixed')) {
- self.fixed(true);
- return false;
- }
- });
- });
- if (settings.popover) {
- self._preBodyHtml = '<div class="' + self.classPrefix + 'arrow"></div>';
- self.classes.add('popover').add('bottom').add(self.isRtl() ? 'end' : 'start');
- }
- self.aria('label', settings.ariaLabel);
- self.aria('labelledby', self._id);
- self.aria('describedby', self.describedBy || self._id + '-none');
- },
- fixed: function(state) {
- var self = this;
- if (self.state.get('fixed') != state) {
- if (self.state.get('rendered')) {
- var viewport = DomUtils.getViewPort();
- if (state) {
- self.layoutRect().y -= viewport.y;
- } else {
- self.layoutRect().y += viewport.y;
- }
- }
- self.classes.toggle('fixed', state);
- self.state.set('fixed', state);
- }
- return self;
- },
- /**
- * Shows the current float panel.
- *
- * @method show
- * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
- */
- show: function() {
- var self = this, i, state = self._super();
- i = visiblePanels.length;
- while (i--) {
- if (visiblePanels[i] === self) {
- break;
- }
- }
- if (i === -1) {
- visiblePanels.push(self);
- }
- return state;
- },
- /**
- * Hides the current float panel.
- *
- * @method hide
- * @return {tinymce.ui.FloatPanel} Current floatpanel instance.
- */
- hide: function() {
- removeVisiblePanel(this);
- addRemove(false, this);
- return this._super();
- },
- /**
- * Hide all visible float panels with he autohide setting enabled. This is for
- * manually hiding floating menus or panels.
- *
- * @method hideAll
- */
- hideAll: function() {
- FloatPanel.hideAll();
- },
- /**
- * Closes the float panel. This will remove the float panel from page and fire the close event.
- *
- * @method close
- */
- close: function() {
- var self = this;
- if (!self.fire('close').isDefaultPrevented()) {
- self.remove();
- addRemove(false, self);
- }
- return self;
- },
- /**
- * Removes the float panel from page.
- *
- * @method remove
- */
- remove: function() {
- removeVisiblePanel(this);
- this._super();
- },
- postRender: function() {
- var self = this;
- if (self.settings.bodyRole) {
- this.getEl('body').setAttribute('role', self.settings.bodyRole);
- }
- return self._super();
- }
- });
- /**
- * Hide all visible float panels with he autohide setting enabled. This is for
- * manually hiding floating menus or panels.
- *
- * @static
- * @method hideAll
- */
- FloatPanel.hideAll = function() {
- var i = visiblePanels.length;
- while (i--) {
- var panel = visiblePanels[i];
- if (panel && panel.settings.autohide) {
- panel.hide();
- visiblePanels.splice(i, 1);
- }
- }
- };
- function removeVisiblePanel(panel) {
- var i;
- i = visiblePanels.length;
- while (i--) {
- if (visiblePanels[i] === panel) {
- visiblePanels.splice(i, 1);
- }
- }
- i = zOrder.length;
- while (i--) {
- if (zOrder[i] === panel) {
- zOrder.splice(i, 1);
- }
- }
- }
- return FloatPanel;
- });
- // Included from: js/tinymce/classes/ui/Window.js
- /**
- * Window.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new window.
- *
- * @-x-less Window.less
- * @class tinymce.ui.Window
- * @extends tinymce.ui.FloatPanel
- */
- define("tinymce/ui/Window", [
- "tinymce/ui/FloatPanel",
- "tinymce/ui/Panel",
- "tinymce/ui/DomUtils",
- "tinymce/dom/DomQuery",
- "tinymce/ui/DragHelper",
- "tinymce/ui/BoxUtils",
- "tinymce/Env",
- "tinymce/util/Delay"
- ], function(FloatPanel, Panel, DomUtils, $, DragHelper, BoxUtils, Env, Delay) {
- "use strict";
- var windows = [], oldMetaValue = '';
- function toggleFullScreenState(state) {
- var noScaleMetaValue = 'width=device-width,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0',
- viewport = $("meta[name=viewport]")[0],
- contentValue;
- if (Env.overrideViewPort === false) {
- return;
- }
- if (!viewport) {
- viewport = document.createElement('meta');
- viewport.setAttribute('name', 'viewport');
- document.getElementsByTagName('head')[0].appendChild(viewport);
- }
- contentValue = viewport.getAttribute('content');
- if (contentValue && typeof oldMetaValue != 'undefined') {
- oldMetaValue = contentValue;
- }
- viewport.setAttribute('content', state ? noScaleMetaValue : oldMetaValue);
- }
- function toggleBodyFullScreenClasses(classPrefix) {
- for (var i = 0; i < windows.length; i++) {
- if (windows[i]._fullscreen) {
- return;
- }
- }
- $([document.documentElement, document.body]).removeClass(classPrefix + 'fullscreen');
- }
- function handleWindowResize() {
- if (!Env.desktop) {
- var lastSize = {
- w: window.innerWidth,
- h: window.innerHeight
- };
- Delay.setInterval(function() {
- var w = window.innerWidth,
- h = window.innerHeight;
- if (lastSize.w != w || lastSize.h != h) {
- lastSize = {
- w: w,
- h: h
- };
- $(window).trigger('resize');
- }
- }, 100);
- }
- function reposition() {
- var i, rect = DomUtils.getWindowSize(), layoutRect;
- for (i = 0; i < windows.length; i++) {
- layoutRect = windows[i].layoutRect();
- windows[i].moveTo(
- windows[i].settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2),
- windows[i].settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2)
- );
- }
- }
- $(window).on('resize', reposition);
- }
- var Window = FloatPanel.extend({
- modal: true,
- Defaults: {
- border: 1,
- layout: 'flex',
- containerCls: 'panel',
- role: 'dialog',
- callbacks: {
- submit: function() {
- this.fire('submit', {data: this.toJSON()});
- },
- close: function() {
- this.close();
- }
- }
- },
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- if (self.isRtl()) {
- self.classes.add('rtl');
- }
- self.classes.add('window');
- self.bodyClasses.add('window-body');
- self.state.set('fixed', true);
- // Create statusbar
- if (settings.buttons) {
- self.statusbar = new Panel({
- layout: 'flex',
- border: '1 0 0 0',
- spacing: 3,
- padding: 10,
- align: 'center',
- pack: self.isRtl() ? 'start' : 'end',
- defaults: {
- type: 'button'
- },
- items: settings.buttons
- });
- self.statusbar.classes.add('foot');
- self.statusbar.parent(self);
- }
- self.on('click', function(e) {
- var closeClass = self.classPrefix + 'close';
- if (DomUtils.hasClass(e.target, closeClass) || DomUtils.hasClass(e.target.parentNode, closeClass)) {
- self.close();
- }
- });
- self.on('cancel', function() {
- self.close();
- });
- self.aria('describedby', self.describedBy || self._id + '-none');
- self.aria('label', settings.title);
- self._fullscreen = false;
- },
- /**
- * Recalculates the positions of the controls in the current container.
- * This is invoked by the reflow method and shouldn't be called directly.
- *
- * @method recalc
- */
- recalc: function() {
- var self = this, statusbar = self.statusbar, layoutRect, width, x, needsRecalc;
- if (self._fullscreen) {
- self.layoutRect(DomUtils.getWindowSize());
- self.layoutRect().contentH = self.layoutRect().innerH;
- }
- self._super();
- layoutRect = self.layoutRect();
- // Resize window based on title width
- if (self.settings.title && !self._fullscreen) {
- width = layoutRect.headerW;
- if (width > layoutRect.w) {
- x = layoutRect.x - Math.max(0, width / 2);
- self.layoutRect({w: width, x: x});
- needsRecalc = true;
- }
- }
- // Resize window based on statusbar width
- if (statusbar) {
- statusbar.layoutRect({w: self.layoutRect().innerW}).recalc();
- width = statusbar.layoutRect().minW + layoutRect.deltaW;
- if (width > layoutRect.w) {
- x = layoutRect.x - Math.max(0, width - layoutRect.w);
- self.layoutRect({w: width, x: x});
- needsRecalc = true;
- }
- }
- // Recalc body and disable auto resize
- if (needsRecalc) {
- self.recalc();
- }
- },
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, layoutRect = self._super(), deltaH = 0, headEl;
- // Reserve vertical space for title
- if (self.settings.title && !self._fullscreen) {
- headEl = self.getEl('head');
- var size = DomUtils.getSize(headEl);
- layoutRect.headerW = size.width;
- layoutRect.headerH = size.height;
- deltaH += layoutRect.headerH;
- }
- // Reserve vertical space for statusbar
- if (self.statusbar) {
- deltaH += self.statusbar.layoutRect().h;
- }
- layoutRect.deltaH += deltaH;
- layoutRect.minH += deltaH;
- //layoutRect.innerH -= deltaH;
- layoutRect.h += deltaH;
- var rect = DomUtils.getWindowSize();
- layoutRect.x = self.settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2);
- layoutRect.y = self.settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2);
- return layoutRect;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout, id = self._id, prefix = self.classPrefix;
- var settings = self.settings, headerHtml = '', footerHtml = '', html = settings.html;
- self.preRender();
- layout.preRender(self);
- if (settings.title) {
- headerHtml = (
- '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
- '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
- '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
- '<button type="button" class="' + prefix + 'close" aria-hidden="true">' +
- '<i class="mce-ico mce-i-remove"></i>' +
- '</button>' +
- '</div>'
- );
- }
- if (settings.url) {
- html = '<iframe src="' + settings.url + '" tabindex="-1"></iframe>';
- }
- if (typeof html == "undefined") {
- html = layout.renderHtml(self);
- }
- if (self.statusbar) {
- footerHtml = self.statusbar.renderHtml();
- }
- return (
- '<div id="' + id + '" class="' + self.classes + '" hidefocus="1">' +
- '<div class="' + self.classPrefix + 'reset" role="application">' +
- headerHtml +
- '<div id="' + id + '-body" class="' + self.bodyClasses + '">' +
- html +
- '</div>' +
- footerHtml +
- '</div>' +
- '</div>'
- );
- },
- /**
- * Switches the window fullscreen mode.
- *
- * @method fullscreen
- * @param {Boolean} state True/false state.
- * @return {tinymce.ui.Window} Current window instance.
- */
- fullscreen: function(state) {
- var self = this, documentElement = document.documentElement, slowRendering, prefix = self.classPrefix, layoutRect;
- if (state != self._fullscreen) {
- $(window).on('resize', function() {
- var time;
- if (self._fullscreen) {
- // Time the layout time if it's to slow use a timeout to not hog the CPU
- if (!slowRendering) {
- time = new Date().getTime();
- var rect = DomUtils.getWindowSize();
- self.moveTo(0, 0).resizeTo(rect.w, rect.h);
- if ((new Date().getTime()) - time > 50) {
- slowRendering = true;
- }
- } else {
- if (!self._timer) {
- self._timer = Delay.setTimeout(function() {
- var rect = DomUtils.getWindowSize();
- self.moveTo(0, 0).resizeTo(rect.w, rect.h);
- self._timer = 0;
- }, 50);
- }
- }
- }
- });
- layoutRect = self.layoutRect();
- self._fullscreen = state;
- if (!state) {
- self.borderBox = BoxUtils.parseBox(self.settings.border);
- self.getEl('head').style.display = '';
- layoutRect.deltaH += layoutRect.headerH;
- $([documentElement, document.body]).removeClass(prefix + 'fullscreen');
- self.classes.remove('fullscreen');
- self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h);
- } else {
- self._initial = {x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h};
- self.borderBox = BoxUtils.parseBox('0');
- self.getEl('head').style.display = 'none';
- layoutRect.deltaH -= layoutRect.headerH + 2;
- $([documentElement, document.body]).addClass(prefix + 'fullscreen');
- self.classes.add('fullscreen');
- var rect = DomUtils.getWindowSize();
- self.moveTo(0, 0).resizeTo(rect.w, rect.h);
- }
- }
- return self.reflow();
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this, startPos;
- setTimeout(function() {
- self.classes.add('in');
- self.fire('open');
- }, 0);
- self._super();
- if (self.statusbar) {
- self.statusbar.postRender();
- }
- self.focus();
- this.dragHelper = new DragHelper(self._id + '-dragh', {
- start: function() {
- startPos = {
- x: self.layoutRect().x,
- y: self.layoutRect().y
- };
- },
- drag: function(e) {
- self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY);
- }
- });
- self.on('submit', function(e) {
- if (!e.isDefaultPrevented()) {
- self.close();
- }
- });
- windows.push(self);
- toggleFullScreenState(true);
- },
- /**
- * Fires a submit event with the serialized form.
- *
- * @method submit
- * @return {Object} Event arguments object.
- */
- submit: function() {
- return this.fire('submit', {data: this.toJSON()});
- },
- /**
- * Removes the current control from DOM and from UI collections.
- *
- * @method remove
- * @return {tinymce.ui.Control} Current control instance.
- */
- remove: function() {
- var self = this, i;
- self.dragHelper.destroy();
- self._super();
- if (self.statusbar) {
- this.statusbar.remove();
- }
- i = windows.length;
- while (i--) {
- if (windows[i] === self) {
- windows.splice(i, 1);
- }
- }
- toggleFullScreenState(windows.length > 0);
- toggleBodyFullScreenClasses(self.classPrefix);
- },
- /**
- * Returns the contentWindow object of the iframe if it exists.
- *
- * @method getContentWindow
- * @return {Window} window object or null.
- */
- getContentWindow: function() {
- var ifr = this.getEl().getElementsByTagName('iframe')[0];
- return ifr ? ifr.contentWindow : null;
- }
- });
- handleWindowResize();
- return Window;
- });
- // Included from: js/tinymce/classes/ui/MessageBox.js
- /**
- * MessageBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is used to create MessageBoxes like alerts/confirms etc.
- *
- * @class tinymce.ui.MessageBox
- * @extends tinymce.ui.FloatPanel
- */
- define("tinymce/ui/MessageBox", [
- "tinymce/ui/Window"
- ], function(Window) {
- "use strict";
- var MessageBox = Window.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- settings = {
- border: 1,
- padding: 20,
- layout: 'flex',
- pack: "center",
- align: "center",
- containerCls: 'panel',
- autoScroll: true,
- buttons: {type: "button", text: "Ok", action: "ok"},
- items: {
- type: "label",
- multiline: true,
- maxWidth: 500,
- maxHeight: 200
- }
- };
- this._super(settings);
- },
- Statics: {
- /**
- * Ok buttons constant.
- *
- * @static
- * @final
- * @field {Number} OK
- */
- OK: 1,
- /**
- * Ok/cancel buttons constant.
- *
- * @static
- * @final
- * @field {Number} OK_CANCEL
- */
- OK_CANCEL: 2,
- /**
- * yes/no buttons constant.
- *
- * @static
- * @final
- * @field {Number} YES_NO
- */
- YES_NO: 3,
- /**
- * yes/no/cancel buttons constant.
- *
- * @static
- * @final
- * @field {Number} YES_NO_CANCEL
- */
- YES_NO_CANCEL: 4,
- /**
- * Constructs a new message box and renders it to the body element.
- *
- * @static
- * @method msgBox
- * @param {Object} settings Name/value object with settings.
- */
- msgBox: function(settings) {
- var buttons, callback = settings.callback || function() {};
- function createButton(text, status, primary) {
- return {
- type: "button",
- text: text,
- subtype: primary ? 'primary' : '',
- onClick: function(e) {
- e.control.parents()[1].close();
- callback(status);
- }
- };
- }
- switch (settings.buttons) {
- case MessageBox.OK_CANCEL:
- buttons = [
- createButton('Ok', true, true),
- createButton('Cancel', false)
- ];
- break;
- case MessageBox.YES_NO:
- case MessageBox.YES_NO_CANCEL:
- buttons = [
- createButton('Yes', 1, true),
- createButton('No', 0)
- ];
- if (settings.buttons == MessageBox.YES_NO_CANCEL) {
- buttons.push(createButton('Cancel', -1));
- }
- break;
- default:
- buttons = [
- createButton('Ok', true, true)
- ];
- break;
- }
- return new Window({
- padding: 20,
- x: settings.x,
- y: settings.y,
- minWidth: 300,
- minHeight: 100,
- layout: "flex",
- pack: "center",
- align: "center",
- buttons: buttons,
- title: settings.title,
- role: 'alertdialog',
- items: {
- type: "label",
- multiline: true,
- maxWidth: 500,
- maxHeight: 200,
- text: settings.text
- },
- onPostRender: function() {
- this.aria('describedby', this.items()[0]._id);
- },
- onClose: settings.onClose,
- onCancel: function() {
- callback(false);
- }
- }).renderTo(document.body).reflow();
- },
- /**
- * Creates a new alert dialog.
- *
- * @method alert
- * @param {Object} settings Settings for the alert dialog.
- * @param {function} [callback] Callback to execute when the user makes a choice.
- */
- alert: function(settings, callback) {
- if (typeof settings == "string") {
- settings = {text: settings};
- }
- settings.callback = callback;
- return MessageBox.msgBox(settings);
- },
- /**
- * Creates a new confirm dialog.
- *
- * @method confirm
- * @param {Object} settings Settings for the confirm dialog.
- * @param {function} [callback] Callback to execute when the user makes a choice.
- */
- confirm: function(settings, callback) {
- if (typeof settings == "string") {
- settings = {text: settings};
- }
- settings.callback = callback;
- settings.buttons = MessageBox.OK_CANCEL;
- return MessageBox.msgBox(settings);
- }
- }
- });
- return MessageBox;
- });
- // Included from: js/tinymce/classes/WindowManager.js
- /**
- * WindowManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles the creation of native windows and dialogs. This class can be extended to provide for example inline dialogs.
- *
- * @class tinymce.WindowManager
- * @example
- * // Opens a new dialog with the file.htm file and the size 320x240
- * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
- * tinymce.activeEditor.windowManager.open({
- * url: 'file.htm',
- * width: 320,
- * height: 240
- * }, {
- * custom_param: 1
- * });
- *
- * // Displays an alert box using the active editors window manager instance
- * tinymce.activeEditor.windowManager.alert('Hello world!');
- *
- * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
- * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
- * if (s)
- * tinymce.activeEditor.windowManager.alert("Ok");
- * else
- * tinymce.activeEditor.windowManager.alert("Cancel");
- * });
- */
- define("tinymce/WindowManager", [
- "tinymce/ui/Window",
- "tinymce/ui/MessageBox"
- ], function(Window, MessageBox) {
- return function(editor) {
- var self = this, windows = [];
- function getTopMostWindow() {
- if (windows.length) {
- return windows[windows.length - 1];
- }
- }
- function fireOpenEvent(win) {
- editor.fire('OpenWindow', {
- win: win
- });
- }
- function fireCloseEvent(win) {
- editor.fire('CloseWindow', {
- win: win
- });
- }
- self.windows = windows;
- editor.on('remove', function() {
- var i = windows.length;
- while (i--) {
- windows[i].close();
- }
- });
- /**
- * Opens a new window.
- *
- * @method open
- * @param {Object} args Optional name/value settings collection contains things like width/height/url etc.
- * @param {Object} params Options like title, file, width, height etc.
- * @option {String} title Window title.
- * @option {String} file URL of the file to open in the window.
- * @option {Number} width Width in pixels.
- * @option {Number} height Height in pixels.
- * @option {Boolean} autoScroll Specifies whether the popup window can have scrollbars if required (i.e. content
- * larger than the popup size specified).
- */
- self.open = function(args, params) {
- var win;
- editor.editorManager.setActive(editor);
- args.title = args.title || ' ';
- // Handle URL
- args.url = args.url || args.file; // Legacy
- if (args.url) {
- args.width = parseInt(args.width || 320, 10);
- args.height = parseInt(args.height || 240, 10);
- }
- // Handle body
- if (args.body) {
- args.items = {
- defaults: args.defaults,
- type: args.bodyType || 'form',
- items: args.body,
- data: args.data,
- callbacks: args.commands
- };
- }
- if (!args.url && !args.buttons) {
- args.buttons = [
- {text: 'Ok', subtype: 'primary', onclick: function() {
- win.find('form')[0].submit();
- }},
- {text: 'Cancel', onclick: function() {
- win.close();
- }}
- ];
- }
- win = new Window(args);
- windows.push(win);
- win.on('close', function() {
- var i = windows.length;
- while (i--) {
- if (windows[i] === win) {
- windows.splice(i, 1);
- }
- }
- if (!windows.length) {
- editor.focus();
- }
- fireCloseEvent(win);
- });
- // Handle data
- if (args.data) {
- win.on('postRender', function() {
- this.find('*').each(function(ctrl) {
- var name = ctrl.name();
- if (name in args.data) {
- ctrl.value(args.data[name]);
- }
- });
- });
- }
- // store args and parameters
- win.features = args || {};
- win.params = params || {};
- // Takes a snapshot in the FocusManager of the selection before focus is lost to dialog
- if (windows.length === 1) {
- editor.nodeChanged();
- }
- win = win.renderTo().reflow();
- fireOpenEvent(win);
- return win;
- };
- /**
- * Creates a alert dialog. Please don't use the blocking behavior of this
- * native version use the callback method instead then it can be extended.
- *
- * @method alert
- * @param {String} message Text to display in the new alert dialog.
- * @param {function} callback Callback function to be executed after the user has selected ok.
- * @param {Object} scope Optional scope to execute the callback in.
- * @example
- * // Displays an alert box using the active editors window manager instance
- * tinymce.activeEditor.windowManager.alert('Hello world!');
- */
- self.alert = function(message, callback, scope) {
- var win;
- win = MessageBox.alert(message, function() {
- if (callback) {
- callback.call(scope || this);
- } else {
- editor.focus();
- }
- });
- win.on('close', function() {
- fireCloseEvent(win);
- });
- fireOpenEvent(win);
- };
- /**
- * Creates a confirm dialog. Please don't use the blocking behavior of this
- * native version use the callback method instead then it can be extended.
- *
- * @method confirm
- * @param {String} message Text to display in the new confirm dialog.
- * @param {function} callback Callback function to be executed after the user has selected ok or cancel.
- * @param {Object} scope Optional scope to execute the callback in.
- * @example
- * // Displays an confirm box and an alert message will be displayed depending on what you choose in the confirm
- * tinymce.activeEditor.windowManager.confirm("Do you want to do something", function(s) {
- * if (s)
- * tinymce.activeEditor.windowManager.alert("Ok");
- * else
- * tinymce.activeEditor.windowManager.alert("Cancel");
- * });
- */
- self.confirm = function(message, callback, scope) {
- var win;
- win = MessageBox.confirm(message, function(state) {
- callback.call(scope || this, state);
- });
- win.on('close', function() {
- fireCloseEvent(win);
- });
- fireOpenEvent(win);
- };
- /**
- * Closes the top most window.
- *
- * @method close
- */
- self.close = function() {
- if (getTopMostWindow()) {
- getTopMostWindow().close();
- }
- };
- /**
- * Returns the params of the last window open call. This can be used in iframe based
- * dialog to get params passed from the tinymce plugin.
- *
- * @example
- * var dialogArguments = top.tinymce.activeEditor.windowManager.getParams();
- *
- * @method getParams
- * @return {Object} Name/value object with parameters passed from windowManager.open call.
- */
- self.getParams = function() {
- return getTopMostWindow() ? getTopMostWindow().params : null;
- };
- /**
- * Sets the params of the last opened window.
- *
- * @method setParams
- * @param {Object} params Params object to set for the last opened window.
- */
- self.setParams = function(params) {
- if (getTopMostWindow()) {
- getTopMostWindow().params = params;
- }
- };
- /**
- * Returns the currently opened window objects.
- *
- * @method getWindows
- * @return {Array} Array of the currently opened windows.
- */
- self.getWindows = function() {
- return windows;
- };
- };
- });
- // Included from: js/tinymce/classes/ui/Tooltip.js
- /**
- * Tooltip.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a tooltip instance.
- *
- * @-x-less ToolTip.less
- * @class tinymce.ui.ToolTip
- * @extends tinymce.ui.Control
- * @mixes tinymce.ui.Movable
- */
- define("tinymce/ui/Tooltip", [
- "tinymce/ui/Control",
- "tinymce/ui/Movable"
- ], function(Control, Movable) {
- return Control.extend({
- Mixins: [Movable],
- Defaults: {
- classes: 'widget tooltip tooltip-n'
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, prefix = self.classPrefix;
- return (
- '<div id="' + self._id + '" class="' + self.classes + '" role="presentation">' +
- '<div class="' + prefix + 'tooltip-arrow"></div>' +
- '<div class="' + prefix + 'tooltip-inner">' + self.encode(self.state.get('text')) + '</div>' +
- '</div>'
- );
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:text', function(e) {
- self.getEl().lastChild.innerHTML = self.encode(e.value);
- });
- return self._super();
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, style, rect;
- style = self.getEl().style;
- rect = self._layoutRect;
- style.left = rect.x + 'px';
- style.top = rect.y + 'px';
- style.zIndex = 0xFFFF + 0xFFFF;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Widget.js
- /**
- * Widget.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Widget base class a widget is a control that has a tooltip and some basic states.
- *
- * @class tinymce.ui.Widget
- * @extends tinymce.ui.Control
- */
- define("tinymce/ui/Widget", [
- "tinymce/ui/Control",
- "tinymce/ui/Tooltip"
- ], function(Control, Tooltip) {
- "use strict";
- var tooltip;
- var Widget = Control.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} tooltip Tooltip text to display when hovering.
- * @setting {Boolean} autofocus True if the control should be focused when rendered.
- * @setting {String} text Text to display inside widget.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- settings = self.settings;
- self.canFocus = true;
- if (settings.tooltip && Widget.tooltips !== false) {
- self.on('mouseenter', function(e) {
- var tooltip = self.tooltip().moveTo(-0xFFFF);
- if (e.control == self) {
- var rel = tooltip.text(settings.tooltip).show().testMoveRel(self.getEl(), ['bc-tc', 'bc-tl', 'bc-tr']);
- tooltip.classes.toggle('tooltip-n', rel == 'bc-tc');
- tooltip.classes.toggle('tooltip-nw', rel == 'bc-tl');
- tooltip.classes.toggle('tooltip-ne', rel == 'bc-tr');
- tooltip.moveRel(self.getEl(), rel);
- } else {
- tooltip.hide();
- }
- });
- self.on('mouseleave mousedown click', function() {
- self.tooltip().hide();
- });
- }
- self.aria('label', settings.ariaLabel || settings.tooltip);
- },
- /**
- * Returns the current tooltip instance.
- *
- * @method tooltip
- * @return {tinymce.ui.Tooltip} Tooltip instance.
- */
- tooltip: function() {
- if (!tooltip) {
- tooltip = new Tooltip({type: 'tooltip'});
- tooltip.renderTo();
- }
- return tooltip;
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this, settings = self.settings;
- self._super();
- if (!self.parent() && (settings.width || settings.height)) {
- self.initLayoutRect();
- self.repaint();
- }
- if (settings.autofocus) {
- self.focus();
- }
- },
- bindStates: function() {
- var self = this;
- function disable(state) {
- self.aria('disabled', state);
- self.classes.toggle('disabled', state);
- }
- function active(state) {
- self.aria('pressed', state);
- self.classes.toggle('active', state);
- }
- self.state.on('change:disabled', function(e) {
- disable(e.value);
- });
- self.state.on('change:active', function(e) {
- active(e.value);
- });
- if (self.state.get('disabled')) {
- disable(true);
- }
- if (self.state.get('active')) {
- active(true);
- }
- return self._super();
- },
- /**
- * Removes the current control from DOM and from UI collections.
- *
- * @method remove
- * @return {tinymce.ui.Control} Current control instance.
- */
- remove: function() {
- this._super();
- if (tooltip) {
- tooltip.remove();
- tooltip = null;
- }
- }
- });
- return Widget;
- });
- // Included from: js/tinymce/classes/ui/Progress.js
- /**
- * Progress.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Progress control.
- *
- * @-x-less Progress.less
- * @class tinymce.ui.Progress
- * @extends tinymce.ui.Control
- */
- define("tinymce/ui/Progress", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- return Widget.extend({
- Defaults: {
- value: 0
- },
- init: function(settings) {
- var self = this;
- self._super(settings);
- self.classes.add('progress');
- if (!self.settings.filter) {
- self.settings.filter = function(value) {
- return Math.round(value);
- };
- }
- },
- renderHtml: function() {
- var self = this, id = self._id, prefix = this.classPrefix;
- return (
- '<div id="' + id + '" class="' + self.classes + '">' +
- '<div class="' + prefix + 'bar-container">' +
- '<div class="' + prefix + 'bar"></div>' +
- '</div>' +
- '<div class="' + prefix + 'text">0%</div>' +
- '</div>'
- );
- },
- postRender: function() {
- var self = this;
- self._super();
- self.value(self.settings.value);
- return self;
- },
- bindStates: function() {
- var self = this;
- function setValue(value) {
- value = self.settings.filter(value);
- self.getEl().lastChild.innerHTML = value + '%';
- self.getEl().firstChild.firstChild.style.width = value + '%';
- }
- self.state.on('change:value', function(e) {
- setValue(e.value);
- });
- setValue(self.state.get('value'));
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Notification.js
- /**
- * Notification.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a notification instance.
- *
- * @-x-less Notification.less
- * @class tinymce.ui.Notification
- * @extends tinymce.ui.Container
- * @mixes tinymce.ui.Movable
- */
- define("tinymce/ui/Notification", [
- "tinymce/ui/Control",
- "tinymce/ui/Movable",
- "tinymce/ui/Progress",
- "tinymce/util/Delay"
- ], function(Control, Movable, Progress, Delay) {
- return Control.extend({
- Mixins: [Movable],
- Defaults: {
- classes: 'widget notification'
- },
- init: function(settings) {
- var self = this;
- self._super(settings);
- if (settings.text) {
- self.text(settings.text);
- }
- if (settings.icon) {
- self.icon = settings.icon;
- }
- if (settings.color) {
- self.color = settings.color;
- }
- if (settings.type) {
- self.classes.add('notification-' + settings.type);
- }
- if (settings.timeout && (settings.timeout < 0 || settings.timeout > 0) && !settings.closeButton) {
- self.closeButton = false;
- } else {
- self.classes.add('has-close');
- self.closeButton = true;
- }
- if (settings.progressBar) {
- self.progressBar = new Progress();
- }
- self.on('click', function(e) {
- if (e.target.className.indexOf(self.classPrefix + 'close') != -1) {
- self.close();
- }
- });
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, prefix = self.classPrefix, icon = '', closeButton = '', progressBar = '', notificationStyle = '';
- if (self.icon) {
- icon = '<i class="' + prefix + 'ico' + ' ' + prefix + 'i-' + self.icon + '"></i>';
- }
- if (self.color) {
- notificationStyle = ' style="background-color: ' + self.color + '"';
- }
- if (self.closeButton) {
- closeButton = '<button type="button" class="' + prefix + 'close" aria-hidden="true">\u00d7</button>';
- }
- if (self.progressBar) {
- progressBar = self.progressBar.renderHtml();
- }
- return (
- '<div id="' + self._id + '" class="' + self.classes + '"' + notificationStyle + ' role="presentation">' +
- icon +
- '<div class="' + prefix + 'notification-inner">' + self.state.get('text') + '</div>' +
- progressBar +
- closeButton +
- '</div>'
- );
- },
- postRender: function() {
- var self = this;
- Delay.setTimeout(function() {
- self.$el.addClass(self.classPrefix + 'in');
- });
- return self._super();
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:text', function(e) {
- self.getEl().childNodes[1].innerHTML = e.value;
- });
- if (self.progressBar) {
- self.progressBar.bindStates();
- }
- return self._super();
- },
- close: function() {
- var self = this;
- if (!self.fire('close').isDefaultPrevented()) {
- self.remove();
- }
- return self;
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, style, rect;
- style = self.getEl().style;
- rect = self._layoutRect;
- style.left = rect.x + 'px';
- style.top = rect.y + 'px';
- style.zIndex = 0xFFFF + 0xFFFF;
- }
- });
- });
- // Included from: js/tinymce/classes/NotificationManager.js
- /**
- * NotificationManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class handles the creation of TinyMCE's notifications.
- *
- * @class tinymce.notificationManager
- * @example
- * // Opens a new notification of type "error" with text "An error occurred."
- * tinymce.activeEditor.notificationManager.open({
- * text: 'An error occurred.',
- * type: 'error'
- * });
- */
- define("tinymce/NotificationManager", [
- "tinymce/ui/Notification",
- "tinymce/util/Delay"
- ], function(Notification, Delay) {
- return function(editor) {
- var self = this, notifications = [];
- function getLastNotification() {
- if (notifications.length) {
- return notifications[notifications.length - 1];
- }
- }
- self.notifications = notifications;
- function resizeWindowEvent() {
- Delay.requestAnimationFrame(function() {
- prePositionNotifications();
- positionNotifications();
- });
- }
- // Since the viewport will change based on the present notifications, we need to move them all to the
- // top left of the viewport to give an accurate size measurement so we can position them later.
- function prePositionNotifications() {
- for (var i = 0; i < notifications.length; i++) {
- notifications[i].moveTo(0, 0);
- }
- }
- function positionNotifications() {
- if (notifications.length > 0) {
- var firstItem = notifications.slice(0, 1)[0];
- var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
- firstItem.moveRel(container, 'tc-tc');
- if (notifications.length > 1) {
- for (var i = 1; i < notifications.length; i++) {
- notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc');
- }
- }
- }
- }
- editor.on('remove', function() {
- var i = notifications.length;
- while (i--) {
- notifications[i].close();
- }
- });
- editor.on('ResizeEditor', positionNotifications);
- editor.on('ResizeWindow', resizeWindowEvent);
- /**
- * Opens a new notification.
- *
- * @method open
- * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc.
- */
- self.open = function(args) {
- var notif;
- editor.editorManager.setActive(editor);
- notif = new Notification(args);
- notifications.push(notif);
- //If we have a timeout value
- if (args.timeout > 0) {
- notif.timer = setTimeout(function() {
- notif.close();
- }, args.timeout);
- }
- notif.on('close', function() {
- var i = notifications.length;
- if (notif.timer) {
- editor.getWin().clearTimeout(notif.timer);
- }
- while (i--) {
- if (notifications[i] === notif) {
- notifications.splice(i, 1);
- }
- }
- positionNotifications();
- });
- notif.renderTo();
- positionNotifications();
- return notif;
- };
- /**
- * Closes the top most notification.
- *
- * @method close
- */
- self.close = function() {
- if (getLastNotification()) {
- getLastNotification().close();
- }
- };
- /**
- * Returns the currently opened notification objects.
- *
- * @method getNotifications
- * @return {Array} Array of the currently opened notifications.
- */
- self.getNotifications = function() {
- return notifications;
- };
- editor.on('SkinLoaded', function() {
- var serviceMessage = editor.settings.service_message;
- if (serviceMessage) {
- editor.notificationManager.open({
- text: serviceMessage,
- type: 'warning',
- timeout: 0,
- icon: ''
- });
- }
- });
- //self.positionNotifications = positionNotifications;
- };
- });
- // Included from: js/tinymce/classes/dom/NodePath.js
- /**
- * NodePath.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Handles paths of nodes within an element.
- *
- * @private
- * @class tinymce.dom.NodePath
- */
- define("tinymce/dom/NodePath", [
- "tinymce/dom/DOMUtils"
- ], function(DOMUtils) {
- function create(rootNode, targetNode, normalized) {
- var path = [];
- for (; targetNode && targetNode != rootNode; targetNode = targetNode.parentNode) {
- path.push(DOMUtils.nodeIndex(targetNode, normalized));
- }
- return path;
- }
- function resolve(rootNode, path) {
- var i, node, children;
- for (node = rootNode, i = path.length - 1; i >= 0; i--) {
- children = node.childNodes;
- if (path[i] > children.length - 1) {
- return null;
- }
- node = children[path[i]];
- }
- return node;
- }
- return {
- create: create,
- resolve: resolve
- };
- });
- // Included from: js/tinymce/classes/util/Quirks.js
- /**
- * Quirks.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- *
- * @ignore-file
- */
- /**
- * This file includes fixes for various browser quirks it's made to make it easy to add/remove browser specific fixes.
- *
- * @private
- * @class tinymce.util.Quirks
- */
- define("tinymce/util/Quirks", [
- "tinymce/util/VK",
- "tinymce/dom/RangeUtils",
- "tinymce/dom/TreeWalker",
- "tinymce/dom/NodePath",
- "tinymce/html/Node",
- "tinymce/html/Entities",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/util/Delay",
- "tinymce/caret/CaretContainer",
- "tinymce/caret/CaretPosition",
- "tinymce/caret/CaretWalker"
- ], function(VK, RangeUtils, TreeWalker, NodePath, Node, Entities, Env, Tools, Delay, CaretContainer, CaretPosition, CaretWalker) {
- return function(editor) {
- var each = Tools.each, $ = editor.$;
- var BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
- settings = editor.settings, parser = editor.parser, serializer = editor.serializer;
- var isGecko = Env.gecko, isIE = Env.ie, isWebKit = Env.webkit;
- var mceInternalUrlPrefix = 'data:text/mce-internal,';
- var mceInternalDataType = isIE ? 'Text' : 'URL';
- /**
- * Executes a command with a specific state this can be to enable/disable browser editing features.
- */
- function setEditorCommandState(cmd, state) {
- try {
- editor.getDoc().execCommand(cmd, false, state);
- } catch (ex) {
- // Ignore
- }
- }
- /**
- * Returns current IE document mode.
- */
- function getDocumentMode() {
- var documentMode = editor.getDoc().documentMode;
- return documentMode ? documentMode : 6;
- }
- /**
- * Returns true/false if the event is prevented or not.
- *
- * @private
- * @param {Event} e Event object.
- * @return {Boolean} true/false if the event is prevented or not.
- */
- function isDefaultPrevented(e) {
- return e.isDefaultPrevented();
- }
- /**
- * Sets Text/URL data on the event's dataTransfer object to a special data:text/mce-internal url.
- * This is to workaround the inability to set custom contentType on IE and Safari.
- * The editor's selected content is encoded into this url so drag and drop between editors will work.
- *
- * @private
- * @param {DragEvent} e Event object
- */
- function setMceInternalContent(e) {
- var selectionHtml, internalContent;
- if (e.dataTransfer) {
- if (editor.selection.isCollapsed() && e.target.tagName == 'IMG') {
- selection.select(e.target);
- }
- selectionHtml = editor.selection.getContent();
- // Safari/IE doesn't support custom dataTransfer items so we can only use URL and Text
- if (selectionHtml.length > 0) {
- internalContent = mceInternalUrlPrefix + escape(editor.id) + ',' + escape(selectionHtml);
- e.dataTransfer.setData(mceInternalDataType, internalContent);
- }
- }
- }
- /**
- * Gets content of special data:text/mce-internal url on the event's dataTransfer object.
- * This is to workaround the inability to set custom contentType on IE and Safari.
- * The editor's selected content is encoded into this url so drag and drop between editors will work.
- *
- * @private
- * @param {DragEvent} e Event object
- * @returns {String} mce-internal content
- */
- function getMceInternalContent(e) {
- var internalContent;
- if (e.dataTransfer) {
- internalContent = e.dataTransfer.getData(mceInternalDataType);
- if (internalContent && internalContent.indexOf(mceInternalUrlPrefix) >= 0) {
- internalContent = internalContent.substr(mceInternalUrlPrefix.length).split(',');
- return {
- id: unescape(internalContent[0]),
- html: unescape(internalContent[1])
- };
- }
- }
- return null;
- }
- /**
- * Inserts contents using the paste clipboard command if it's available if it isn't it will fallback
- * to the core command.
- *
- * @private
- * @param {String} content Content to insert at selection.
- */
- function insertClipboardContents(content) {
- if (editor.queryCommandSupported('mceInsertClipboardContent')) {
- editor.execCommand('mceInsertClipboardContent', false, {content: content});
- } else {
- editor.execCommand('mceInsertContent', false, content);
- }
- }
- /**
- * Fixes a WebKit bug when deleting contents using backspace or delete key.
- * WebKit will produce a span element if you delete across two block elements.
- *
- * Example:
- * <h1>a</h1><p>|b</p>
- *
- * Will produce this on backspace:
- * <h1>a<span style="<all runtime styles>">b</span></p>
- *
- * This fixes the backspace to produce:
- * <h1>a|b</p>
- *
- * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
- *
- * This fixes the following delete scenarios:
- * 1. Delete by pressing backspace key.
- * 2. Delete by pressing delete key.
- * 3. Delete by pressing backspace key with ctrl/cmd (Word delete).
- * 4. Delete by pressing delete key with ctrl/cmd (Word delete).
- * 5. Delete by drag/dropping contents inside the editor.
- * 6. Delete by using Cut Ctrl+X/Cmd+X.
- * 7. Delete by selecting contents and writing a character.
- *
- * This code is a ugly hack since writing full custom delete logic for just this bug
- * fix seemed like a huge task. I hope we can remove this before the year 2030.
- */
- function cleanupStylesWhenDeleting() {
- var doc = editor.getDoc(), dom = editor.dom, selection = editor.selection;
- var MutationObserver = window.MutationObserver, olderWebKit, dragStartRng;
- // Add mini polyfill for older WebKits
- // TODO: Remove this when old Safari versions gets updated
- if (!MutationObserver) {
- olderWebKit = true;
- MutationObserver = function() {
- var records = [], target;
- function nodeInsert(e) {
- var target = e.relatedNode || e.target;
- records.push({target: target, addedNodes: [target]});
- }
- function attrModified(e) {
- var target = e.relatedNode || e.target;
- records.push({target: target, attributeName: e.attrName});
- }
- this.observe = function(node) {
- target = node;
- target.addEventListener('DOMSubtreeModified', nodeInsert, false);
- target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
- target.addEventListener('DOMNodeInserted', nodeInsert, false);
- target.addEventListener('DOMAttrModified', attrModified, false);
- };
- this.disconnect = function() {
- target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
- target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
- target.removeEventListener('DOMNodeInserted', nodeInsert, false);
- target.removeEventListener('DOMAttrModified', attrModified, false);
- };
- this.takeRecords = function() {
- return records;
- };
- };
- }
- function isTrailingBr(node) {
- var blockElements = dom.schema.getBlockElements(), rootNode = editor.getBody();
- if (node.nodeName != 'BR') {
- return false;
- }
- for (; node != rootNode && !blockElements[node.nodeName]; node = node.parentNode) {
- if (node.nextSibling) {
- return false;
- }
- }
- return true;
- }
- function isSiblingsIgnoreWhiteSpace(node1, node2) {
- var node;
- for (node = node1.nextSibling; node && node != node2; node = node.nextSibling) {
- if (node.nodeType == 3 && $.trim(node.data).length === 0) {
- continue;
- }
- if (node !== node2) {
- return false;
- }
- }
- return node === node2;
- }
- function findCaretNode(node, forward, startNode) {
- var walker, current, nonEmptyElements;
- nonEmptyElements = dom.schema.getNonEmptyElements();
- walker = new TreeWalker(startNode || node, node);
- while ((current = walker[forward ? 'next' : 'prev']())) {
- if (nonEmptyElements[current.nodeName] && !isTrailingBr(current)) {
- return current;
- }
- if (current.nodeType == 3 && current.data.length > 0) {
- return current;
- }
- }
- }
- function deleteRangeBetweenTextBlocks(rng) {
- var startBlock, endBlock, caretNodeBefore, caretNodeAfter, textBlockElements;
- if (rng.collapsed) {
- return;
- }
- startBlock = dom.getParent(RangeUtils.getNode(rng.startContainer, rng.startOffset), dom.isBlock);
- endBlock = dom.getParent(RangeUtils.getNode(rng.endContainer, rng.endOffset), dom.isBlock);
- textBlockElements = editor.schema.getTextBlockElements();
- if (startBlock == endBlock) {
- return;
- }
- if (!textBlockElements[startBlock.nodeName] || !textBlockElements[endBlock.nodeName]) {
- return;
- }
- if (dom.getContentEditable(startBlock) === "false" || dom.getContentEditable(endBlock) === "false") {
- return;
- }
- rng.deleteContents();
- caretNodeBefore = findCaretNode(startBlock, false);
- caretNodeAfter = findCaretNode(endBlock, true);
- if (!dom.isEmpty(endBlock)) {
- $(startBlock).append(endBlock.childNodes);
- }
- $(endBlock).remove();
- if (caretNodeBefore) {
- if (caretNodeBefore.nodeType == 1) {
- if (caretNodeBefore.nodeName == "BR") {
- rng.setStartBefore(caretNodeBefore);
- rng.setEndBefore(caretNodeBefore);
- } else {
- rng.setStartAfter(caretNodeBefore);
- rng.setEndAfter(caretNodeBefore);
- }
- } else {
- rng.setStart(caretNodeBefore, caretNodeBefore.data.length);
- rng.setEnd(caretNodeBefore, caretNodeBefore.data.length);
- }
- } else if (caretNodeAfter) {
- if (caretNodeAfter.nodeType == 1) {
- rng.setStartBefore(caretNodeAfter);
- rng.setEndBefore(caretNodeAfter);
- } else {
- rng.setStart(caretNodeAfter, 0);
- rng.setEnd(caretNodeAfter, 0);
- }
- }
- selection.setRng(rng);
- return true;
- }
- function expandBetweenBlocks(rng, isForward) {
- var caretNode, targetCaretNode, textBlock, targetTextBlock, container, offset;
- if (!rng.collapsed) {
- return rng;
- }
- container = rng.startContainer;
- offset = rng.startOffset;
- if (container.nodeType == 3) {
- if (isForward) {
- if (offset < container.data.length) {
- return rng;
- }
- } else {
- if (offset > 0) {
- return rng;
- }
- }
- }
- caretNode = RangeUtils.getNode(rng.startContainer, rng.startOffset);
- textBlock = dom.getParent(caretNode, dom.isBlock);
- targetCaretNode = findCaretNode(editor.getBody(), isForward, caretNode);
- targetTextBlock = dom.getParent(targetCaretNode, dom.isBlock);
- if (!caretNode || !targetCaretNode) {
- return rng;
- }
- if (targetTextBlock && textBlock != targetTextBlock) {
- if (!isForward) {
- if (!isSiblingsIgnoreWhiteSpace(targetTextBlock, textBlock)) {
- return rng;
- }
- if (targetCaretNode.nodeType == 1) {
- if (targetCaretNode.nodeName == "BR") {
- rng.setStartBefore(targetCaretNode);
- } else {
- rng.setStartAfter(targetCaretNode);
- }
- } else {
- rng.setStart(targetCaretNode, targetCaretNode.data.length);
- }
- if (caretNode.nodeType == 1) {
- rng.setEnd(caretNode, 0);
- } else {
- rng.setEndBefore(caretNode);
- }
- } else {
- if (!isSiblingsIgnoreWhiteSpace(textBlock, targetTextBlock)) {
- return rng;
- }
- if (caretNode.nodeType == 1) {
- if (caretNode.nodeName == "BR") {
- rng.setStartBefore(caretNode);
- } else {
- rng.setStartAfter(caretNode);
- }
- } else {
- rng.setStart(caretNode, caretNode.data.length);
- }
- if (targetCaretNode.nodeType == 1) {
- rng.setEnd(targetCaretNode, 0);
- } else {
- rng.setEndBefore(targetCaretNode);
- }
- }
- }
- return rng;
- }
- function handleTextBlockMergeDelete(isForward) {
- var rng = selection.getRng();
- rng = expandBetweenBlocks(rng, isForward);
- if (deleteRangeBetweenTextBlocks(rng)) {
- return true;
- }
- }
- /**
- * This retains the formatting if the last character is to be deleted.
- *
- * Backspace on this: <p><b><i>a|</i></b></p> would become <p>|</p> in WebKit.
- * With this patch: <p><b><i>|<br></i></b></p>
- */
- function handleLastBlockCharacterDelete(isForward, rng) {
- var path, blockElm, newBlockElm, clonedBlockElm, sibling,
- container, offset, br, currentFormatNodes;
- function cloneTextBlockWithFormats(blockElm, node) {
- currentFormatNodes = $(node).parents().filter(function(idx, node) {
- return !!editor.schema.getTextInlineElements()[node.nodeName];
- });
- newBlockElm = blockElm.cloneNode(false);
- currentFormatNodes = Tools.map(currentFormatNodes, function(formatNode) {
- formatNode = formatNode.cloneNode(false);
- if (newBlockElm.hasChildNodes()) {
- formatNode.appendChild(newBlockElm.firstChild);
- newBlockElm.appendChild(formatNode);
- } else {
- newBlockElm.appendChild(formatNode);
- }
- newBlockElm.appendChild(formatNode);
- return formatNode;
- });
- if (currentFormatNodes.length) {
- br = dom.create('br');
- currentFormatNodes[0].appendChild(br);
- dom.replace(newBlockElm, blockElm);
- rng.setStartBefore(br);
- rng.setEndBefore(br);
- editor.selection.setRng(rng);
- return br;
- }
- return null;
- }
- function isTextBlock(node) {
- return node && editor.schema.getTextBlockElements()[node.tagName];
- }
- if (!rng.collapsed) {
- return;
- }
- container = rng.startContainer;
- offset = rng.startOffset;
- blockElm = dom.getParent(container, dom.isBlock);
- if (!isTextBlock(blockElm)) {
- return;
- }
- if (container.nodeType == 1) {
- container = container.childNodes[offset];
- if (container && container.tagName != 'BR') {
- return;
- }
- if (isForward) {
- sibling = blockElm.nextSibling;
- } else {
- sibling = blockElm.previousSibling;
- }
- if (dom.isEmpty(blockElm) && isTextBlock(sibling) && dom.isEmpty(sibling)) {
- if (cloneTextBlockWithFormats(blockElm, container)) {
- dom.remove(sibling);
- return true;
- }
- }
- } else if (container.nodeType == 3) {
- path = NodePath.create(blockElm, container);
- clonedBlockElm = blockElm.cloneNode(true);
- container = NodePath.resolve(clonedBlockElm, path);
- if (isForward) {
- if (offset >= container.data.length) {
- return;
- }
- container.deleteData(offset, 1);
- } else {
- if (offset <= 0) {
- return;
- }
- container.deleteData(offset - 1, 1);
- }
- if (dom.isEmpty(clonedBlockElm)) {
- return cloneTextBlockWithFormats(blockElm, container);
- }
- }
- }
- function customDelete(isForward) {
- var mutationObserver, rng, caretElement;
- if (handleTextBlockMergeDelete(isForward)) {
- return;
- }
- Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
- // Mark existing spans
- if (elm.tagName == 'SPAN') {
- elm.setAttribute('mce-data-marked', 1);
- }
- // Make sure all elements has a data-mce-style attribute
- if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
- editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style'));
- }
- });
- // Observe added nodes and style attribute changes
- mutationObserver = new MutationObserver(function() {});
- mutationObserver.observe(editor.getDoc(), {
- childList: true,
- attributes: true,
- subtree: true,
- attributeFilter: ['style']
- });
- editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);
- rng = editor.selection.getRng();
- caretElement = rng.startContainer.parentNode;
- Tools.each(mutationObserver.takeRecords(), function(record) {
- if (!dom.isChildOf(record.target, editor.getBody())) {
- return;
- }
- // Restore style attribute to previous value
- if (record.attributeName == "style") {
- var oldValue = record.target.getAttribute('data-mce-style');
- if (oldValue) {
- record.target.setAttribute("style", oldValue);
- } else {
- record.target.removeAttribute("style");
- }
- }
- // Remove all spans that aren't marked and retain selection
- Tools.each(record.addedNodes, function(node) {
- if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
- var offset, container;
- if (node == caretElement) {
- offset = rng.startOffset;
- container = node.firstChild;
- }
- dom.remove(node, true);
- if (container) {
- rng.setStart(container, offset);
- rng.setEnd(container, offset);
- editor.selection.setRng(rng);
- }
- }
- });
- });
- mutationObserver.disconnect();
- // Remove any left over marks
- Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
- span.removeAttribute('mce-data-marked');
- });
- }
- editor.on('keydown', function(e) {
- var isForward = e.keyCode == DELETE, isMetaOrCtrl = e.ctrlKey || e.metaKey;
- if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
- var rng = editor.selection.getRng(), container = rng.startContainer, offset = rng.startOffset;
- // Shift+Delete is cut
- if (isForward && e.shiftKey) {
- return;
- }
- if (handleLastBlockCharacterDelete(isForward, rng)) {
- e.preventDefault();
- return;
- }
- // Ignore non meta delete in the where there is text before/after the caret
- if (!isMetaOrCtrl && rng.collapsed && container.nodeType == 3) {
- if (isForward ? offset < container.data.length : offset > 0) {
- return;
- }
- }
- e.preventDefault();
- if (isMetaOrCtrl) {
- editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", e.metaKey ? "lineboundary" : "word");
- }
- customDelete(isForward);
- }
- });
- // Handle case where text is deleted by typing over
- editor.on('keypress', function(e) {
- if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) {
- var rng, currentFormatNodes, fragmentNode, blockParent, caretNode, charText;
- rng = editor.selection.getRng();
- charText = String.fromCharCode(e.charCode);
- e.preventDefault();
- // Keep track of current format nodes
- currentFormatNodes = $(rng.startContainer).parents().filter(function(idx, node) {
- return !!editor.schema.getTextInlineElements()[node.nodeName];
- });
- customDelete(true);
- // Check if the browser removed them
- currentFormatNodes = currentFormatNodes.filter(function(idx, node) {
- return !$.contains(editor.getBody(), node);
- });
- // Then re-add them
- if (currentFormatNodes.length) {
- fragmentNode = dom.createFragment();
- currentFormatNodes.each(function(idx, formatNode) {
- formatNode = formatNode.cloneNode(false);
- if (fragmentNode.hasChildNodes()) {
- formatNode.appendChild(fragmentNode.firstChild);
- fragmentNode.appendChild(formatNode);
- } else {
- caretNode = formatNode;
- fragmentNode.appendChild(formatNode);
- }
- fragmentNode.appendChild(formatNode);
- });
- caretNode.appendChild(editor.getDoc().createTextNode(charText));
- // Prevent edge case where older WebKit would add an extra BR element
- blockParent = dom.getParent(rng.startContainer, dom.isBlock);
- if (dom.isEmpty(blockParent)) {
- $(blockParent).empty().append(fragmentNode);
- } else {
- rng.insertNode(fragmentNode);
- }
- rng.setStart(caretNode.firstChild, 1);
- rng.setEnd(caretNode.firstChild, 1);
- editor.selection.setRng(rng);
- } else {
- editor.selection.setContent(charText);
- }
- }
- });
- editor.addCommand('Delete', function() {
- customDelete();
- });
- editor.addCommand('ForwardDelete', function() {
- customDelete(true);
- });
- // Older WebKits doesn't properly handle the clipboard so we can't add the rest
- if (olderWebKit) {
- return;
- }
- editor.on('dragstart', function(e) {
- dragStartRng = selection.getRng();
- setMceInternalContent(e);
- });
- editor.on('drop', function(e) {
- if (!isDefaultPrevented(e)) {
- var internalContent = getMceInternalContent(e);
- if (internalContent) {
- e.preventDefault();
- // Safari has a weird issue where drag/dropping images sometimes
- // produces a green plus icon. When this happens the caretRangeFromPoint
- // will return "null" even though the x, y coordinate is correct.
- // But if we detach the insert from the drop event we will get a proper range
- Delay.setEditorTimeout(editor, function() {
- var pointRng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, doc);
- if (dragStartRng) {
- selection.setRng(dragStartRng);
- dragStartRng = null;
- }
- customDelete();
- selection.setRng(pointRng);
- insertClipboardContents(internalContent.html);
- });
- }
- }
- });
- editor.on('cut', function(e) {
- if (!isDefaultPrevented(e) && e.clipboardData && !editor.selection.isCollapsed()) {
- e.preventDefault();
- e.clipboardData.clearData();
- e.clipboardData.setData('text/html', editor.selection.getContent());
- e.clipboardData.setData('text/plain', editor.selection.getContent({format: 'text'}));
- // Needed delay for https://code.google.com/p/chromium/issues/detail?id=363288#c3
- // Nested delete/forwardDelete not allowed on execCommand("cut")
- // This is ugly but not sure how to work around it otherwise
- Delay.setEditorTimeout(editor, function() {
- customDelete(true);
- });
- }
- });
- }
- /**
- * Makes sure that the editor body becomes empty when backspace or delete is pressed in empty editors.
- *
- * For example:
- * <p><b>|</b></p>
- *
- * Or:
- * <h1>|</h1>
- *
- * Or:
- * [<h1></h1>]
- */
- function emptyEditorWhenDeleting() {
- function serializeRng(rng) {
- var body = dom.create("body");
- var contents = rng.cloneContents();
- body.appendChild(contents);
- return selection.serializer.serialize(body, {format: 'html'});
- }
- function allContentsSelected(rng) {
- if (!rng.setStart) {
- if (rng.item) {
- return false;
- }
- var bodyRng = rng.duplicate();
- bodyRng.moveToElementText(editor.getBody());
- return RangeUtils.compareRanges(rng, bodyRng);
- }
- var selection = serializeRng(rng);
- var allRng = dom.createRng();
- allRng.selectNode(editor.getBody());
- var allSelection = serializeRng(allRng);
- return selection === allSelection;
- }
- editor.on('keydown', function(e) {
- var keyCode = e.keyCode, isCollapsed, body;
- // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
- if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
- isCollapsed = editor.selection.isCollapsed();
- body = editor.getBody();
- // Selection is collapsed but the editor isn't empty
- if (isCollapsed && !dom.isEmpty(body)) {
- return;
- }
- // Selection isn't collapsed but not all the contents is selected
- if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
- return;
- }
- // Manually empty the editor
- e.preventDefault();
- editor.setContent('');
- if (body.firstChild && dom.isBlock(body.firstChild)) {
- editor.selection.setCursorLocation(body.firstChild, 0);
- } else {
- editor.selection.setCursorLocation(body, 0);
- }
- editor.nodeChanged();
- }
- });
- }
- /**
- * WebKit doesn't select all the nodes in the body when you press Ctrl+A.
- * IE selects more than the contents <body>[<p>a</p>]</body> instead of <body><p>[a]</p]</body> see bug #6438
- * This selects the whole body so that backspace/delete logic will delete everything
- */
- function selectAll() {
- editor.shortcuts.add('meta+a', null, 'SelectAll');
- }
- /**
- * WebKit has a weird issue where it some times fails to properly convert keypresses to input method keystrokes.
- * The IME on Mac doesn't initialize when it doesn't fire a proper focus event.
- *
- * This seems to happen when the user manages to click the documentElement element then the window doesn't get proper focus until
- * you enter a character into the editor.
- *
- * It also happens when the first focus in made to the body.
- *
- * See: https://bugs.webkit.org/show_bug.cgi?id=83566
- */
- function inputMethodFocus() {
- if (!editor.settings.content_editable) {
- // Case 1 IME doesn't initialize if you focus the document
- // Disabled since it was interferring with the cE=false logic
- // Also coultn't reproduce the issue on Safari 9
- /*dom.bind(editor.getDoc(), 'focusin', function() {
- selection.setRng(selection.getRng());
- });*/
- // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
- // Needs to be both down/up due to weird rendering bug on Chrome Windows
- dom.bind(editor.getDoc(), 'mousedown mouseup', function(e) {
- var rng;
- if (e.target == editor.getDoc().documentElement) {
- rng = selection.getRng();
- editor.getBody().focus();
- if (e.type == 'mousedown') {
- if (CaretContainer.isCaretContainer(rng.startContainer)) {
- return;
- }
- // Edge case for mousedown, drag select and mousedown again within selection on Chrome Windows to render caret
- selection.placeCaretAt(e.clientX, e.clientY);
- } else {
- selection.setRng(rng);
- }
- }
- });
- }
- }
- /**
- * Backspacing in FireFox/IE from a paragraph into a horizontal rule results in a floating text node because the
- * browser just deletes the paragraph - the browser fails to merge the text node with a horizontal rule so it is
- * left there. TinyMCE sees a floating text node and wraps it in a paragraph on the key up event (ForceBlocks.js
- * addRootBlocks), meaning the action does nothing. With this code, FireFox/IE matche the behaviour of other
- * browsers.
- *
- * It also fixes a bug on Firefox where it's impossible to delete HR elements.
- */
- function removeHrOnBackspace() {
- editor.on('keydown', function(e) {
- if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
- // Check if there is any HR elements this is faster since getRng on IE 7 & 8 is slow
- if (!editor.getBody().getElementsByTagName('hr').length) {
- return;
- }
- if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
- var node = selection.getNode();
- var previousSibling = node.previousSibling;
- if (node.nodeName == 'HR') {
- dom.remove(node);
- e.preventDefault();
- return;
- }
- if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
- dom.remove(previousSibling);
- e.preventDefault();
- }
- }
- }
- });
- }
- /**
- * Firefox 3.x has an issue where the body element won't get proper focus if you click out
- * side it's rectangle.
- */
- function focusBody() {
- // Fix for a focus bug in FF 3.x where the body element
- // wouldn't get proper focus if the user clicked on the HTML element
- if (!window.Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
- editor.on('mousedown', function(e) {
- if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
- var body = editor.getBody();
- // Blur the body it's focused but not correctly focused
- body.blur();
- // Refocus the body after a little while
- Delay.setEditorTimeout(editor, function() {
- body.focus();
- });
- }
- });
- }
- }
- /**
- * WebKit has a bug where it isn't possible to select image, hr or anchor elements
- * by clicking on them so we need to fake that.
- */
- function selectControlElements() {
- editor.on('click', function(e) {
- var target = e.target;
- // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
- // WebKit can't even do simple things like selecting an image
- // Needs to be the setBaseAndExtend or it will fail to select floated images
- if (/^(IMG|HR)$/.test(target.nodeName) && dom.getContentEditableParent(target) !== "false") {
- e.preventDefault();
- selection.getSel().setBaseAndExtent(target, 0, target, 1);
- editor.nodeChanged();
- }
- if (target.nodeName == 'A' && dom.hasClass(target, 'mce-item-anchor')) {
- e.preventDefault();
- selection.select(target);
- }
- });
- }
- /**
- * Fixes a Gecko bug where the style attribute gets added to the wrong element when deleting between two block elements.
- *
- * Fixes do backspace/delete on this:
- * <p>bla[ck</p><p style="color:red">r]ed</p>
- *
- * Would become:
- * <p>bla|ed</p>
- *
- * Instead of:
- * <p style="color:red">bla|ed</p>
- */
- function removeStylesWhenDeletingAcrossBlockElements() {
- function getAttributeApplyFunction() {
- var template = dom.getAttribs(selection.getStart().cloneNode(false));
- return function() {
- var target = selection.getStart();
- if (target !== editor.getBody()) {
- dom.setAttrib(target, "style", null);
- each(template, function(attr) {
- target.setAttributeNode(attr.cloneNode(true));
- });
- }
- };
- }
- function isSelectionAcrossElements() {
- return !selection.isCollapsed() &&
- dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
- }
- editor.on('keypress', function(e) {
- var applyAttributes;
- if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
- applyAttributes = getAttributeApplyFunction();
- editor.getDoc().execCommand('delete', false, null);
- applyAttributes();
- e.preventDefault();
- return false;
- }
- });
- dom.bind(editor.getDoc(), 'cut', function(e) {
- var applyAttributes;
- if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
- applyAttributes = getAttributeApplyFunction();
- Delay.setEditorTimeout(editor, function() {
- applyAttributes();
- });
- }
- });
- }
- /**
- * Screen readers on IE needs to have the role application set on the body.
- */
- function ensureBodyHasRoleApplication() {
- document.body.setAttribute("role", "application");
- }
- /**
- * Backspacing into a table behaves differently depending upon browser type.
- * Therefore, disable Backspace when cursor immediately follows a table.
- */
- function disableBackspaceIntoATable() {
- editor.on('keydown', function(e) {
- if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
- if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
- var previousSibling = selection.getNode().previousSibling;
- if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
- e.preventDefault();
- return false;
- }
- }
- }
- });
- }
- /**
- * Old IE versions can't properly render BR elements in PRE tags white in contentEditable mode. So this
- * logic adds a \n before the BR so that it will get rendered.
- */
- function addNewLinesBeforeBrInPre() {
- // IE8+ rendering mode does the right thing with BR in PRE
- if (getDocumentMode() > 7) {
- return;
- }
- // Enable display: none in area and add a specific class that hides all BR elements in PRE to
- // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
- setEditorCommandState('RespectVisibilityInDesign', true);
- editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
- dom.addClass(editor.getBody(), 'mceHideBrInPre');
- // Adds a \n before all BR elements in PRE to get them visual
- parser.addNodeFilter('pre', function(nodes) {
- var i = nodes.length, brNodes, j, brElm, sibling;
- while (i--) {
- brNodes = nodes[i].getAll('br');
- j = brNodes.length;
- while (j--) {
- brElm = brNodes[j];
- // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
- sibling = brElm.prev;
- if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
- sibling.value += '\n';
- } else {
- brElm.parent.insert(new Node('#text', 3), brElm, true).value = '\n';
- }
- }
- }
- });
- // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
- serializer.addNodeFilter('pre', function(nodes) {
- var i = nodes.length, brNodes, j, brElm, sibling;
- while (i--) {
- brNodes = nodes[i].getAll('br');
- j = brNodes.length;
- while (j--) {
- brElm = brNodes[j];
- sibling = brElm.prev;
- if (sibling && sibling.type == 3) {
- sibling.value = sibling.value.replace(/\r?\n$/, '');
- }
- }
- }
- });
- }
- /**
- * Moves style width/height to attribute width/height when the user resizes an image on IE.
- */
- function removePreSerializedStylesWhenSelectingControls() {
- dom.bind(editor.getBody(), 'mouseup', function() {
- var value, node = selection.getNode();
- // Moved styles to attributes on IMG eements
- if (node.nodeName == 'IMG') {
- // Convert style width to width attribute
- if ((value = dom.getStyle(node, 'width'))) {
- dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
- dom.setStyle(node, 'width', '');
- }
- // Convert style height to height attribute
- if ((value = dom.getStyle(node, 'height'))) {
- dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
- dom.setStyle(node, 'height', '');
- }
- }
- });
- }
- /**
- * Removes a blockquote when backspace is pressed at the beginning of it.
- *
- * For example:
- * <blockquote><p>|x</p></blockquote>
- *
- * Becomes:
- * <p>|x</p>
- */
- function removeBlockQuoteOnBackSpace() {
- // Add block quote deletion handler
- editor.on('keydown', function(e) {
- var rng, container, offset, root, parent;
- if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
- return;
- }
- rng = selection.getRng();
- container = rng.startContainer;
- offset = rng.startOffset;
- root = dom.getRoot();
- parent = container;
- if (!rng.collapsed || offset !== 0) {
- return;
- }
- while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
- parent = parent.parentNode;
- }
- // Is the cursor at the beginning of a blockquote?
- if (parent.tagName === 'BLOCKQUOTE') {
- // Remove the blockquote
- editor.formatter.toggle('blockquote', null, parent);
- // Move the caret to the beginning of container
- rng = dom.createRng();
- rng.setStart(container, 0);
- rng.setEnd(container, 0);
- selection.setRng(rng);
- }
- });
- }
- /**
- * Sets various Gecko editing options on mouse down and before a execCommand to disable inline table editing that is broken etc.
- */
- function setGeckoEditingOptions() {
- function setOpts() {
- refreshContentEditable();
- setEditorCommandState("StyleWithCSS", false);
- setEditorCommandState("enableInlineTableEditing", false);
- if (!settings.object_resizing) {
- setEditorCommandState("enableObjectResizing", false);
- }
- }
- if (!settings.readonly) {
- editor.on('BeforeExecCommand MouseDown', setOpts);
- }
- }
- /**
- * Fixes a gecko link bug, when a link is placed at the end of block elements there is
- * no way to move the caret behind the link. This fix adds a bogus br element after the link.
- *
- * For example this:
- * <p><b><a href="#">x</a></b></p>
- *
- * Becomes this:
- * <p><b><a href="#">x</a></b><br></p>
- */
- function addBrAfterLastLinks() {
- function fixLinks() {
- each(dom.select('a'), function(node) {
- var parentNode = node.parentNode, root = dom.getRoot();
- if (parentNode.lastChild === node) {
- while (parentNode && !dom.isBlock(parentNode)) {
- if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
- return;
- }
- parentNode = parentNode.parentNode;
- }
- dom.add(parentNode, 'br', {'data-mce-bogus': 1});
- }
- });
- }
- editor.on('SetContent ExecCommand', function(e) {
- if (e.type == "setcontent" || e.command === 'mceInsertLink') {
- fixLinks();
- }
- });
- }
- /**
- * WebKit will produce DIV elements here and there by default. But since TinyMCE uses paragraphs by
- * default we want to change that behavior.
- */
- function setDefaultBlockType() {
- if (settings.forced_root_block) {
- editor.on('init', function() {
- setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
- });
- }
- }
- /**
- * Deletes the selected image on IE instead of navigating to previous page.
- */
- function deleteControlItemOnBackSpace() {
- editor.on('keydown', function(e) {
- var rng;
- if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
- rng = editor.getDoc().selection.createRange();
- if (rng && rng.item) {
- e.preventDefault();
- editor.undoManager.beforeChange();
- dom.remove(rng.item(0));
- editor.undoManager.add();
- }
- }
- });
- }
- /**
- * IE10 doesn't properly render block elements with the right height until you add contents to them.
- * This fixes that by adding a padding-right to all empty text block elements.
- * See: https://connect.microsoft.com/IE/feedback/details/743881
- */
- function renderEmptyBlocksFix() {
- var emptyBlocksCSS;
- // IE10+
- if (getDocumentMode() >= 10) {
- emptyBlocksCSS = '';
- each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
- emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
- });
- editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
- }
- }
- /**
- * Old IE versions can't retain contents within noscript elements so this logic will store the contents
- * as a attribute and the insert that value as it's raw text when the DOM is serialized.
- */
- function keepNoScriptContents() {
- if (getDocumentMode() < 9) {
- parser.addNodeFilter('noscript', function(nodes) {
- var i = nodes.length, node, textNode;
- while (i--) {
- node = nodes[i];
- textNode = node.firstChild;
- if (textNode) {
- node.attr('data-mce-innertext', textNode.value);
- }
- }
- });
- serializer.addNodeFilter('noscript', function(nodes) {
- var i = nodes.length, node, textNode, value;
- while (i--) {
- node = nodes[i];
- textNode = nodes[i].firstChild;
- if (textNode) {
- textNode.value = Entities.decode(textNode.value);
- } else {
- // Old IE can't retain noscript value so an attribute is used to store it
- value = node.attributes.map['data-mce-innertext'];
- if (value) {
- node.attr('data-mce-innertext', null);
- textNode = new Node('#text', 3);
- textNode.value = value;
- textNode.raw = true;
- node.append(textNode);
- }
- }
- }
- });
- }
- }
- /**
- * IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode.
- */
- function fixCaretSelectionOfDocumentElementOnIe() {
- var doc = dom.doc, body = doc.body, started, startRng, htmlElm;
- // Return range from point or null if it failed
- function rngFromPoint(x, y) {
- var rng = body.createTextRange();
- try {
- rng.moveToPoint(x, y);
- } catch (ex) {
- // IE sometimes throws and exception, so lets just ignore it
- rng = null;
- }
- return rng;
- }
- // Fires while the selection is changing
- function selectionChange(e) {
- var pointRng;
- // Check if the button is down or not
- if (e.button) {
- // Create range from mouse position
- pointRng = rngFromPoint(e.x, e.y);
- if (pointRng) {
- // Check if pointRange is before/after selection then change the endPoint
- if (pointRng.compareEndPoints('StartToStart', startRng) > 0) {
- pointRng.setEndPoint('StartToStart', startRng);
- } else {
- pointRng.setEndPoint('EndToEnd', startRng);
- }
- pointRng.select();
- }
- } else {
- endSelection();
- }
- }
- // Removes listeners
- function endSelection() {
- var rng = doc.selection.createRange();
- // If the range is collapsed then use the last start range
- if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) {
- startRng.select();
- }
- dom.unbind(doc, 'mouseup', endSelection);
- dom.unbind(doc, 'mousemove', selectionChange);
- startRng = started = 0;
- }
- // Make HTML element unselectable since we are going to handle selection by hand
- doc.documentElement.unselectable = true;
- // Detect when user selects outside BODY
- dom.bind(doc, 'mousedown contextmenu', function(e) {
- if (e.target.nodeName === 'HTML') {
- if (started) {
- endSelection();
- }
- // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
- htmlElm = doc.documentElement;
- if (htmlElm.scrollHeight > htmlElm.clientHeight) {
- return;
- }
- started = 1;
- // Setup start position
- startRng = rngFromPoint(e.x, e.y);
- if (startRng) {
- // Listen for selection change events
- dom.bind(doc, 'mouseup', endSelection);
- dom.bind(doc, 'mousemove', selectionChange);
- dom.getRoot().focus();
- startRng.select();
- }
- }
- });
- }
- /**
- * Fixes selection issues where the caret can be placed between two inline elements like <b>a</b>|<b>b</b>
- * this fix will lean the caret right into the closest inline element.
- */
- function normalizeSelection() {
- // 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
- editor.on('keyup focusin mouseup', function(e) {
- if (e.keyCode != 65 || !VK.metaKeyPressed(e)) {
- selection.normalize();
- }
- }, true);
- }
- /**
- * Forces Gecko to render a broken image icon if it fails to load an image.
- */
- function showBrokenImageIcon() {
- editor.contentStyles.push(
- 'img:-moz-broken {' +
- '-moz-force-broken-image-icon:1;' +
- 'min-width:24px;' +
- 'min-height:24px' +
- '}'
- );
- }
- /**
- * iOS has a bug where it's impossible to type if the document has a touchstart event
- * bound and the user touches the document while having the on screen keyboard visible.
- *
- * The touch event moves the focus to the parent document while having the caret inside the iframe
- * this fix moves the focus back into the iframe document.
- */
- function restoreFocusOnKeyDown() {
- if (!editor.inline) {
- editor.on('keydown', function() {
- if (document.activeElement == document.body) {
- editor.getWin().focus();
- }
- });
- }
- }
- /**
- * IE 11 has an annoying issue where you can't move focus into the editor
- * by clicking on the white area HTML element. We used to be able to to fix this with
- * the fixCaretSelectionOfDocumentElementOnIe fix. But since M$ removed the selection
- * object it's not possible anymore. So we need to hack in a ungly CSS to force the
- * body to be at least 150px. If the user clicks the HTML element out side this 150px region
- * we simply move the focus into the first paragraph. Not ideal since you loose the
- * positioning of the caret but goot enough for most cases.
- */
- function bodyHeight() {
- if (!editor.inline) {
- editor.contentStyles.push('body {min-height: 150px}');
- editor.on('click', function(e) {
- var rng;
- if (e.target.nodeName == 'HTML') {
- // Edge seems to only need focus if we set the range
- // the caret will become invisible and moved out of the iframe!!
- if (Env.ie > 11) {
- editor.getBody().focus();
- return;
- }
- // Need to store away non collapsed ranges since the focus call will mess that up see #7382
- rng = editor.selection.getRng();
- editor.getBody().focus();
- editor.selection.setRng(rng);
- editor.selection.normalize();
- editor.nodeChanged();
- }
- });
- }
- }
- /**
- * Firefox on Mac OS will move the browser back to the previous page if you press CMD+Left arrow.
- * You might then loose all your work so we need to block that behavior and replace it with our own.
- */
- function blockCmdArrowNavigation() {
- if (Env.mac) {
- editor.on('keydown', function(e) {
- if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode == 37 || e.keyCode == 39)) {
- e.preventDefault();
- editor.selection.getSel().modify('move', e.keyCode == 37 ? 'backward' : 'forward', 'lineboundary');
- }
- });
- }
- }
- /**
- * Disables the autolinking in IE 9+ this is then re-enabled by the autolink plugin.
- */
- function disableAutoUrlDetect() {
- setEditorCommandState("AutoUrlDetect", false);
- }
- /**
- * iOS 7.1 introduced two new bugs:
- * 1) It's possible to open links within a contentEditable area by clicking on them.
- * 2) If you hold down the finger it will display the link/image touch callout menu.
- */
- function tapLinksAndImages() {
- editor.on('click', function(e) {
- var elm = e.target;
- do {
- if (elm.tagName === 'A') {
- e.preventDefault();
- return;
- }
- } while ((elm = elm.parentNode));
- });
- editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
- }
- /**
- * iOS Safari and possible other browsers have a bug where it won't fire
- * a click event when a contentEditable is focused. This function fakes click events
- * by using touchstart/touchend and measuring the time and distance travelled.
- */
- /*
- function touchClickEvent() {
- editor.on('touchstart', function(e) {
- var elm, time, startTouch, changedTouches;
- elm = e.target;
- time = new Date().getTime();
- changedTouches = e.changedTouches;
- if (!changedTouches || changedTouches.length > 1) {
- return;
- }
- startTouch = changedTouches[0];
- editor.once('touchend', function(e) {
- var endTouch = e.changedTouches[0], args;
- if (new Date().getTime() - time > 500) {
- return;
- }
- if (Math.abs(startTouch.clientX - endTouch.clientX) > 5) {
- return;
- }
- if (Math.abs(startTouch.clientY - endTouch.clientY) > 5) {
- return;
- }
- args = {
- target: elm
- };
- each('pageX pageY clientX clientY screenX screenY'.split(' '), function(key) {
- args[key] = endTouch[key];
- });
- args = editor.fire('click', args);
- if (!args.isDefaultPrevented()) {
- // iOS WebKit can't place the caret properly once
- // you bind touch events so we need to do this manually
- // TODO: Expand to the closest word? Touble tap still works.
- editor.selection.placeCaretAt(endTouch.clientX, endTouch.clientY);
- editor.nodeChanged();
- }
- });
- });
- }
- */
- /**
- * WebKit has a bug where it will allow forms to be submitted if they are inside a contentEditable element.
- * For example this: <form><button></form>
- */
- function blockFormSubmitInsideEditor() {
- editor.on('init', function() {
- editor.dom.bind(editor.getBody(), 'submit', function(e) {
- e.preventDefault();
- });
- });
- }
- /**
- * Sometimes WebKit/Blink generates BR elements with the Apple-interchange-newline class.
- *
- * Scenario:
- * 1) Create a table 2x2.
- * 2) Select and copy cells A2-B2.
- * 3) Paste and it will add BR element to table cell.
- */
- function removeAppleInterchangeBrs() {
- parser.addNodeFilter('br', function(nodes) {
- var i = nodes.length;
- while (i--) {
- if (nodes[i].attr('class') == 'Apple-interchange-newline') {
- nodes[i].remove();
- }
- }
- });
- }
- /**
- * IE cannot set custom contentType's on drag events, and also does not properly drag/drop between
- * editors. This uses a special data:text/mce-internal URL to pass data when drag/drop between editors.
- */
- function ieInternalDragAndDrop() {
- editor.on('dragstart', function(e) {
- setMceInternalContent(e);
- });
- editor.on('drop', function(e) {
- if (!isDefaultPrevented(e)) {
- var internalContent = getMceInternalContent(e);
- if (internalContent && internalContent.id != editor.id) {
- e.preventDefault();
- var rng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, editor.getDoc());
- selection.setRng(rng);
- insertClipboardContents(internalContent.html);
- }
- }
- });
- }
- function refreshContentEditable() {
- var body, parent;
- // Check if the editor was hidden and the re-initialize contentEditable mode by removing and adding the body again
- if (isHidden()) {
- body = editor.getBody();
- parent = body.parentNode;
- parent.removeChild(body);
- parent.appendChild(body);
- body.focus();
- }
- }
- function isHidden() {
- var sel;
- if (!isGecko) {
- return 0;
- }
- // Weird, wheres that cursor selection?
- sel = editor.selection.getSel();
- return (!sel || !sel.rangeCount || sel.rangeCount === 0);
- }
- /**
- * Properly empties the editor if all contents is selected and deleted this to
- * prevent empty paragraphs from being produced at beginning/end of contents.
- */
- function emptyEditorOnDeleteEverything() {
- function isEverythingSelected(editor) {
- var caretWalker = new CaretWalker(editor.getBody());
- var rng = editor.selection.getRng();
- var startCaretPos = CaretPosition.fromRangeStart(rng);
- var endCaretPos = CaretPosition.fromRangeEnd(rng);
- return !editor.selection.isCollapsed() && !caretWalker.prev(startCaretPos) && !caretWalker.next(endCaretPos);
- }
- // Type over case delete and insert this won't cover typeover with a IME but at least it covers the common case
- editor.on('keypress', function (e) {
- if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) {
- if (isEverythingSelected(editor)) {
- e.preventDefault();
- editor.setContent(String.fromCharCode(e.charCode));
- editor.selection.select(editor.getBody(), true);
- editor.selection.collapse(false);
- editor.nodeChanged();
- }
- }
- });
- editor.on('keydown', function (e) {
- var keyCode = e.keyCode;
- if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
- if (isEverythingSelected(editor)) {
- e.preventDefault();
- editor.setContent('');
- editor.nodeChanged();
- }
- }
- });
- }
- // All browsers
- removeBlockQuoteOnBackSpace();
- emptyEditorWhenDeleting();
- // Windows phone will return a range like [body, 0] on mousedown so
- // it will always normalize to the wrong location
- if (!Env.windowsPhone) {
- normalizeSelection();
- }
- // WebKit
- if (isWebKit) {
- emptyEditorOnDeleteEverything();
- cleanupStylesWhenDeleting();
- inputMethodFocus();
- selectControlElements();
- setDefaultBlockType();
- blockFormSubmitInsideEditor();
- disableBackspaceIntoATable();
- removeAppleInterchangeBrs();
- //touchClickEvent();
- // iOS
- if (Env.iOS) {
- restoreFocusOnKeyDown();
- bodyHeight();
- tapLinksAndImages();
- } else {
- selectAll();
- }
- }
- // IE
- if (isIE && Env.ie < 11) {
- removeHrOnBackspace();
- ensureBodyHasRoleApplication();
- addNewLinesBeforeBrInPre();
- removePreSerializedStylesWhenSelectingControls();
- deleteControlItemOnBackSpace();
- renderEmptyBlocksFix();
- keepNoScriptContents();
- fixCaretSelectionOfDocumentElementOnIe();
- }
- if (Env.ie >= 11) {
- bodyHeight();
- disableBackspaceIntoATable();
- }
- if (Env.ie) {
- selectAll();
- disableAutoUrlDetect();
- ieInternalDragAndDrop();
- }
- // Gecko
- if (isGecko) {
- emptyEditorOnDeleteEverything();
- removeHrOnBackspace();
- focusBody();
- removeStylesWhenDeletingAcrossBlockElements();
- setGeckoEditingOptions();
- addBrAfterLastLinks();
- showBrokenImageIcon();
- blockCmdArrowNavigation();
- disableBackspaceIntoATable();
- }
- return {
- refreshContentEditable: refreshContentEditable,
- isHidden: isHidden
- };
- };
- });
- // Included from: js/tinymce/classes/EditorObservable.js
- /**
- * EditorObservable.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This mixin contains the event logic for the tinymce.Editor class.
- *
- * @mixin tinymce.EditorObservable
- * @extends tinymce.util.Observable
- */
- define("tinymce/EditorObservable", [
- "tinymce/util/Observable",
- "tinymce/dom/DOMUtils",
- "tinymce/util/Tools"
- ], function(Observable, DOMUtils, Tools) {
- var DOM = DOMUtils.DOM, customEventRootDelegates;
- /**
- * Returns the event target so for the specified event. Some events fire
- * only on document, some fire on documentElement etc. This also handles the
- * custom event root setting where it returns that element instead of the body.
- *
- * @private
- * @param {tinymce.Editor} editor Editor instance to get event target from.
- * @param {String} eventName Name of the event for example "click".
- * @return {Element/Document} HTML Element or document target to bind on.
- */
- function getEventTarget(editor, eventName) {
- if (eventName == 'selectionchange') {
- return editor.getDoc();
- }
- // Need to bind mousedown/mouseup etc to document not body in iframe mode
- // Since the user might click on the HTML element not the BODY
- if (!editor.inline && /^mouse|touch|click|contextmenu|drop|dragover|dragend/.test(eventName)) {
- return editor.getDoc().documentElement;
- }
- // Bind to event root instead of body if it's defined
- if (editor.settings.event_root) {
- if (!editor.eventRoot) {
- editor.eventRoot = DOM.select(editor.settings.event_root)[0];
- }
- return editor.eventRoot;
- }
- return editor.getBody();
- }
- /**
- * Binds a event delegate for the specified name this delegate will fire
- * the event to the editor dispatcher.
- *
- * @private
- * @param {tinymce.Editor} editor Editor instance to get event target from.
- * @param {String} eventName Name of the event for example "click".
- */
- function bindEventDelegate(editor, eventName) {
- var eventRootElm = getEventTarget(editor, eventName), delegate;
- function isListening(editor) {
- return !editor.hidden && !editor.readonly;
- }
- if (!editor.delegates) {
- editor.delegates = {};
- }
- if (editor.delegates[eventName]) {
- return;
- }
- if (editor.settings.event_root) {
- if (!customEventRootDelegates) {
- customEventRootDelegates = {};
- editor.editorManager.on('removeEditor', function() {
- var name;
- if (!editor.editorManager.activeEditor) {
- if (customEventRootDelegates) {
- for (name in customEventRootDelegates) {
- editor.dom.unbind(getEventTarget(editor, name));
- }
- customEventRootDelegates = null;
- }
- }
- });
- }
- if (customEventRootDelegates[eventName]) {
- return;
- }
- delegate = function(e) {
- var target = e.target, editors = editor.editorManager.editors, i = editors.length;
- while (i--) {
- var body = editors[i].getBody();
- if (body === target || DOM.isChildOf(target, body)) {
- if (isListening(editors[i])) {
- editors[i].fire(eventName, e);
- }
- }
- }
- };
- customEventRootDelegates[eventName] = delegate;
- DOM.bind(eventRootElm, eventName, delegate);
- } else {
- delegate = function(e) {
- if (isListening(editor)) {
- editor.fire(eventName, e);
- }
- };
- DOM.bind(eventRootElm, eventName, delegate);
- editor.delegates[eventName] = delegate;
- }
- }
- var EditorObservable = {
- /**
- * Bind any pending event delegates. This gets executed after the target body/document is created.
- *
- * @private
- */
- bindPendingEventDelegates: function() {
- var self = this;
- Tools.each(self._pendingNativeEvents, function(name) {
- bindEventDelegate(self, name);
- });
- },
- /**
- * Toggles a native event on/off this is called by the EventDispatcher when
- * the first native event handler is added and when the last native event handler is removed.
- *
- * @private
- */
- toggleNativeEvent: function(name, state) {
- var self = this;
- // Never bind focus/blur since the FocusManager fakes those
- if (name == "focus" || name == "blur") {
- return;
- }
- if (state) {
- if (self.initialized) {
- bindEventDelegate(self, name);
- } else {
- if (!self._pendingNativeEvents) {
- self._pendingNativeEvents = [name];
- } else {
- self._pendingNativeEvents.push(name);
- }
- }
- } else if (self.initialized) {
- self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
- delete self.delegates[name];
- }
- },
- /**
- * Unbinds all native event handlers that means delegates, custom events bound using the Events API etc.
- *
- * @private
- */
- unbindAllNativeEvents: function() {
- var self = this, name;
- if (self.delegates) {
- for (name in self.delegates) {
- self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
- }
- delete self.delegates;
- }
- if (!self.inline) {
- self.getBody().onload = null;
- self.dom.unbind(self.getWin());
- self.dom.unbind(self.getDoc());
- }
- self.dom.unbind(self.getBody());
- self.dom.unbind(self.getContainer());
- }
- };
- EditorObservable = Tools.extend({}, Observable, EditorObservable);
- return EditorObservable;
- });
- // Included from: js/tinymce/classes/Mode.js
- /**
- * Mode.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Mode switcher logic.
- *
- * @private
- * @class tinymce.Mode
- */
- define("tinymce/Mode", [], function() {
- function setEditorCommandState(editor, cmd, state) {
- try {
- editor.getDoc().execCommand(cmd, false, state);
- } catch (ex) {
- // Ignore
- }
- }
- function clickBlocker(editor) {
- var target, handler;
- target = editor.getBody();
- handler = function(e) {
- if (editor.dom.getParents(e.target, 'a').length > 0) {
- e.preventDefault();
- }
- };
- editor.dom.bind(target, 'click', handler);
- return {
- unbind: function() {
- editor.dom.unbind(target, 'click', handler);
- }
- };
- }
- function toggleReadOnly(editor, state) {
- if (editor._clickBlocker) {
- editor._clickBlocker.unbind();
- editor._clickBlocker = null;
- }
- if (state) {
- editor._clickBlocker = clickBlocker(editor);
- editor.selection.controlSelection.hideResizeRect();
- editor.readonly = true;
- editor.getBody().contentEditable = false;
- } else {
- editor.readonly = false;
- editor.getBody().contentEditable = true;
- setEditorCommandState(editor, "StyleWithCSS", false);
- setEditorCommandState(editor, "enableInlineTableEditing", false);
- setEditorCommandState(editor, "enableObjectResizing", false);
- editor.focus();
- editor.nodeChanged();
- }
- }
- function setMode(editor, mode) {
- var currentMode = editor.readonly ? 'readonly' : 'design';
- if (mode == currentMode) {
- return;
- }
- if (editor.initialized) {
- toggleReadOnly(editor, mode == 'readonly');
- } else {
- editor.on('init', function() {
- toggleReadOnly(editor, mode == 'readonly');
- });
- }
- // Event is NOT preventable
- editor.fire('SwitchMode', {mode: mode});
- }
- return {
- setMode: setMode
- };
- });
- // Included from: js/tinymce/classes/Shortcuts.js
- /**
- * Shortcuts.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Contains all logic for handling of keyboard shortcuts.
- *
- * @class tinymce.Shortcuts
- * @example
- * editor.shortcuts.add('ctrl+a', function() {});
- * editor.shortcuts.add('meta+a', function() {}); // "meta" maps to Command on Mac and Ctrl on PC
- * editor.shortcuts.add('ctrl+alt+a', function() {});
- * editor.shortcuts.add('access+a', function() {}); // "access" maps to ctrl+alt on Mac and shift+alt on PC
- */
- define("tinymce/Shortcuts", [
- "tinymce/util/Tools",
- "tinymce/Env"
- ], function(Tools, Env) {
- var each = Tools.each, explode = Tools.explode;
- var keyCodeLookup = {
- "f9": 120,
- "f10": 121,
- "f11": 122
- };
- var modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
- return function(editor) {
- var self = this, shortcuts = {}, pendingPatterns = [];
- function parseShortcut(pattern) {
- var id, key, shortcut = {};
- // Parse modifiers and keys ctrl+alt+b for example
- each(explode(pattern, '+'), function(value) {
- if (value in modifierNames) {
- shortcut[value] = true;
- } else {
- // Allow numeric keycodes like ctrl+219 for ctrl+[
- if (/^[0-9]{2,}$/.test(value)) {
- shortcut.keyCode = parseInt(value, 10);
- } else {
- shortcut.charCode = value.charCodeAt(0);
- shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
- }
- }
- });
- // Generate unique id for modifier combination and set default state for unused modifiers
- id = [shortcut.keyCode];
- for (key in modifierNames) {
- if (shortcut[key]) {
- id.push(key);
- } else {
- shortcut[key] = false;
- }
- }
- shortcut.id = id.join(',');
- // Handle special access modifier differently depending on Mac/Win
- if (shortcut.access) {
- shortcut.alt = true;
- if (Env.mac) {
- shortcut.ctrl = true;
- } else {
- shortcut.shift = true;
- }
- }
- // Handle special meta modifier differently depending on Mac/Win
- if (shortcut.meta) {
- if (Env.mac) {
- shortcut.meta = true;
- } else {
- shortcut.ctrl = true;
- shortcut.meta = false;
- }
- }
- return shortcut;
- }
- function createShortcut(pattern, desc, cmdFunc, scope) {
- var shortcuts;
- shortcuts = Tools.map(explode(pattern, '>'), parseShortcut);
- shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
- func: cmdFunc,
- scope: scope || editor
- });
- return Tools.extend(shortcuts[0], {
- desc: editor.translate(desc),
- subpatterns: shortcuts.slice(1)
- });
- }
- function hasModifier(e) {
- return e.altKey || e.ctrlKey || e.metaKey;
- }
- function isFunctionKey(e) {
- return e.keyCode >= 112 && e.keyCode <= 123;
- }
- function matchShortcut(e, shortcut) {
- if (!shortcut) {
- return false;
- }
- if (shortcut.ctrl != e.ctrlKey || shortcut.meta != e.metaKey) {
- return false;
- }
- if (shortcut.alt != e.altKey || shortcut.shift != e.shiftKey) {
- return false;
- }
- if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
- e.preventDefault();
- return true;
- }
- return false;
- }
- function executeShortcutAction(shortcut) {
- return shortcut.func ? shortcut.func.call(shortcut.scope) : null;
- }
- editor.on('keyup keypress keydown', function(e) {
- if ((hasModifier(e) || isFunctionKey(e)) && !e.isDefaultPrevented()) {
- each(shortcuts, function(shortcut) {
- if (matchShortcut(e, shortcut)) {
- pendingPatterns = shortcut.subpatterns.slice(0);
- if (e.type == "keydown") {
- executeShortcutAction(shortcut);
- }
- return true;
- }
- });
- if (matchShortcut(e, pendingPatterns[0])) {
- if (pendingPatterns.length === 1) {
- if (e.type == "keydown") {
- executeShortcutAction(pendingPatterns[0]);
- }
- }
- pendingPatterns.shift();
- }
- }
- });
- /**
- * Adds a keyboard shortcut for some command or function.
- *
- * @method addShortcut
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
- * @param {String} desc Text description for the command.
- * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
- * @param {Object} scope Optional scope to execute the function in.
- * @return {Boolean} true/false state if the shortcut was added or not.
- */
- self.add = function(pattern, desc, cmdFunc, scope) {
- var cmd;
- cmd = cmdFunc;
- if (typeof cmdFunc === 'string') {
- cmdFunc = function() {
- editor.execCommand(cmd, false, null);
- };
- } else if (Tools.isArray(cmd)) {
- cmdFunc = function() {
- editor.execCommand(cmd[0], cmd[1], cmd[2]);
- };
- }
- each(explode(Tools.trim(pattern.toLowerCase())), function(pattern) {
- var shortcut = createShortcut(pattern, desc, cmdFunc, scope);
- shortcuts[shortcut.id] = shortcut;
- });
- return true;
- };
- /**
- * Remove a keyboard shortcut by pattern.
- *
- * @method remove
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
- * @return {Boolean} true/false state if the shortcut was removed or not.
- */
- self.remove = function(pattern) {
- var shortcut = createShortcut(pattern);
- if (shortcuts[shortcut.id]) {
- delete shortcuts[shortcut.id];
- return true;
- }
- return false;
- };
- };
- });
- // Included from: js/tinymce/classes/file/Uploader.js
- /**
- * Uploader.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Upload blobs or blob infos to the specified URL or handler.
- *
- * @private
- * @class tinymce.file.Uploader
- * @example
- * var uploader = new Uploader({
- * url: '/upload.php',
- * basePath: '/base/path',
- * credentials: true,
- * handler: function(data, success, failure) {
- * ...
- * }
- * });
- *
- * uploader.upload(blobInfos).then(function(result) {
- * ...
- * });
- */
- define("tinymce/file/Uploader", [
- "tinymce/util/Promise",
- "tinymce/util/Tools",
- "tinymce/util/Fun"
- ], function(Promise, Tools, Fun) {
- return function(uploadStatus, settings) {
- var pendingPromises = {};
- function fileName(blobInfo) {
- var ext, extensions;
- extensions = {
- 'image/jpeg': 'jpg',
- 'image/jpg': 'jpg',
- 'image/gif': 'gif',
- 'image/png': 'png'
- };
- ext = extensions[blobInfo.blob().type.toLowerCase()] || 'dat';
- return blobInfo.id() + '.' + ext;
- }
- function pathJoin(path1, path2) {
- if (path1) {
- return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
- }
- return path2;
- }
- function blobInfoToData(blobInfo) {
- return {
- id: blobInfo.id,
- blob: blobInfo.blob,
- base64: blobInfo.base64,
- filename: Fun.constant(fileName(blobInfo))
- };
- }
- function defaultHandler(blobInfo, success, failure, progress) {
- var xhr, formData;
- xhr = new XMLHttpRequest();
- xhr.open('POST', settings.url);
- xhr.withCredentials = settings.credentials;
- xhr.upload.onprogress = function(e) {
- progress(e.loaded / e.total * 100);
- };
- xhr.onerror = function() {
- failure("Image upload failed due to a XHR Transport error. Code: " + xhr.status);
- };
- xhr.onload = function() {
- var json;
- if (xhr.status != 200) {
- failure("HTTP Error: " + xhr.status);
- return;
- }
- json = JSON.parse(xhr.responseText);
- if (!json || typeof json.location != "string") {
- failure("Invalid JSON: " + xhr.responseText);
- return;
- }
- success(pathJoin(settings.basePath, json.location));
- };
- formData = new FormData();
- formData.append('file', blobInfo.blob(), fileName(blobInfo));
- xhr.send(formData);
- }
- function noUpload() {
- return new Promise(function(resolve) {
- resolve([]);
- });
- }
- function handlerSuccess(blobInfo, url) {
- return {
- url: url,
- blobInfo: blobInfo,
- status: true
- };
- }
- function handlerFailure(blobInfo, error) {
- return {
- url: '',
- blobInfo: blobInfo,
- status: false,
- error: error
- };
- }
- function resolvePending(blobUri, result) {
- Tools.each(pendingPromises[blobUri], function(resolve) {
- resolve(result);
- });
- delete pendingPromises[blobUri];
- }
- function uploadBlobInfo(blobInfo, handler, openNotification) {
- uploadStatus.markPending(blobInfo.blobUri());
- return new Promise(function(resolve) {
- var notification, progress;
- var noop = function() {
- };
- try {
- var closeNotification = function() {
- if (notification) {
- notification.close();
- progress = noop; // Once it's closed it's closed
- }
- };
- var success = function(url) {
- closeNotification();
- uploadStatus.markUploaded(blobInfo.blobUri(), url);
- resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url));
- resolve(handlerSuccess(blobInfo, url));
- };
- var failure = function() {
- closeNotification();
- uploadStatus.removeFailed(blobInfo.blobUri());
- resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, failure));
- resolve(handlerFailure(blobInfo, failure));
- };
- progress = function(percent) {
- if (percent < 0 || percent > 100) {
- return;
- }
- if (!notification) {
- notification = openNotification();
- }
- notification.progressBar.value(percent);
- };
- handler(blobInfoToData(blobInfo), success, failure, progress);
- } catch (ex) {
- resolve(handlerFailure(blobInfo, ex.message));
- }
- });
- }
- function isDefaultHandler(handler) {
- return handler === defaultHandler;
- }
- function pendingUploadBlobInfo(blobInfo) {
- var blobUri = blobInfo.blobUri();
- return new Promise(function(resolve) {
- pendingPromises[blobUri] = pendingPromises[blobUri] || [];
- pendingPromises[blobUri].push(resolve);
- });
- }
- function uploadBlobs(blobInfos, openNotification) {
- blobInfos = Tools.grep(blobInfos, function(blobInfo) {
- return !uploadStatus.isUploaded(blobInfo.blobUri());
- });
- return Promise.all(Tools.map(blobInfos, function(blobInfo) {
- return uploadStatus.isPending(blobInfo.blobUri()) ?
- pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, settings.handler, openNotification);
- }));
- }
- function upload(blobInfos, openNotification) {
- return (!settings.url && isDefaultHandler(settings.handler)) ? noUpload() : uploadBlobs(blobInfos, openNotification);
- }
- settings = Tools.extend({
- credentials: false,
- // We are adding a notify argument to this (at the moment, until it doesn't work)
- handler: defaultHandler
- }, settings);
- return {
- upload: upload
- };
- };
- });
- // Included from: js/tinymce/classes/file/Conversions.js
- /**
- * Conversions.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Converts blob/uris back and forth.
- *
- * @private
- * @class tinymce.file.Conversions
- */
- define("tinymce/file/Conversions", [
- "tinymce/util/Promise"
- ], function(Promise) {
- function blobUriToBlob(url) {
- return new Promise(function(resolve) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url, true);
- xhr.responseType = 'blob';
- xhr.onload = function() {
- if (this.status == 200) {
- resolve(this.response);
- }
- };
- xhr.send();
- });
- }
- function parseDataUri(uri) {
- var type, matches;
- uri = decodeURIComponent(uri).split(',');
- matches = /data:([^;]+)/.exec(uri[0]);
- if (matches) {
- type = matches[1];
- }
- return {
- type: type,
- data: uri[1]
- };
- }
- function dataUriToBlob(uri) {
- return new Promise(function(resolve) {
- var str, arr, i;
- uri = parseDataUri(uri);
- // Might throw error if data isn't proper base64
- try {
- str = atob(uri.data);
- } catch (e) {
- resolve(new Blob([]));
- return;
- }
- arr = new Uint8Array(str.length);
- for (i = 0; i < arr.length; i++) {
- arr[i] = str.charCodeAt(i);
- }
- resolve(new Blob([arr], {type: uri.type}));
- });
- }
- function uriToBlob(url) {
- if (url.indexOf('blob:') === 0) {
- return blobUriToBlob(url);
- }
- if (url.indexOf('data:') === 0) {
- return dataUriToBlob(url);
- }
- return null;
- }
- function blobToDataUri(blob) {
- return new Promise(function(resolve) {
- var reader = new FileReader();
- reader.onloadend = function() {
- resolve(reader.result);
- };
- reader.readAsDataURL(blob);
- });
- }
- return {
- uriToBlob: uriToBlob,
- blobToDataUri: blobToDataUri,
- parseDataUri: parseDataUri
- };
- });
- // Included from: js/tinymce/classes/file/ImageScanner.js
- /**
- * ImageScanner.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Finds images with data uris or blob uris. If data uris are found it will convert them into blob uris.
- *
- * @private
- * @class tinymce.file.ImageScanner
- */
- define("tinymce/file/ImageScanner", [
- "tinymce/util/Promise",
- "tinymce/util/Arr",
- "tinymce/util/Fun",
- "tinymce/file/Conversions",
- "tinymce/Env"
- ], function(Promise, Arr, Fun, Conversions, Env) {
- var count = 0;
- return function(uploadStatus, blobCache) {
- var cachedPromises = {};
- function findAll(elm, predicate) {
- var images, promises;
- function imageToBlobInfo(img, resolve) {
- var base64, blobInfo;
- if (img.src.indexOf('blob:') === 0) {
- blobInfo = blobCache.getByUri(img.src);
- if (blobInfo) {
- resolve({
- image: img,
- blobInfo: blobInfo
- });
- }
- return;
- }
- base64 = Conversions.parseDataUri(img.src).data;
- blobInfo = blobCache.findFirst(function(cachedBlobInfo) {
- return cachedBlobInfo.base64() === base64;
- });
- if (blobInfo) {
- resolve({
- image: img,
- blobInfo: blobInfo
- });
- } else {
- Conversions.uriToBlob(img.src).then(function(blob) {
- var blobInfoId = 'blobid' + (count++),
- blobInfo = blobCache.create(blobInfoId, blob, base64);
- blobCache.add(blobInfo);
- resolve({
- image: img,
- blobInfo: blobInfo
- });
- });
- }
- }
- if (!predicate) {
- predicate = Fun.constant(true);
- }
- images = Arr.filter(elm.getElementsByTagName('img'), function(img) {
- var src = img.src;
- if (!Env.fileApi) {
- return false;
- }
- if (img.hasAttribute('data-mce-bogus')) {
- return false;
- }
- if (img.hasAttribute('data-mce-placeholder')) {
- return false;
- }
- if (!src || src == Env.transparentSrc) {
- return false;
- }
- if (src.indexOf('blob:') === 0) {
- return !uploadStatus.isUploaded(src);
- }
- if (src.indexOf('data:') === 0) {
- return predicate(img);
- }
- return false;
- });
- promises = Arr.map(images, function(img) {
- var newPromise;
- if (cachedPromises[img.src]) {
- // Since the cached promise will return the cached image
- // We need to wrap it and resolve with the actual image
- return new Promise(function(resolve) {
- cachedPromises[img.src].then(function(imageInfo) {
- resolve({
- image: img,
- blobInfo: imageInfo.blobInfo
- });
- });
- });
- }
- newPromise = new Promise(function(resolve) {
- imageToBlobInfo(img, resolve);
- }).then(function(result) {
- delete cachedPromises[result.image.src];
- return result;
- })['catch'](function(error) {
- delete cachedPromises[img.src];
- return error;
- });
- cachedPromises[img.src] = newPromise;
- return newPromise;
- });
- return Promise.all(promises);
- }
- return {
- findAll: findAll
- };
- };
- });
- // Included from: js/tinymce/classes/file/BlobCache.js
- /**
- * BlobCache.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Hold blob info objects where a blob has extra internal information.
- *
- * @private
- * @class tinymce.file.BlobCache
- */
- define("tinymce/file/BlobCache", [
- "tinymce/util/Arr",
- "tinymce/util/Fun"
- ], function(Arr, Fun) {
- return function() {
- var cache = [], constant = Fun.constant;
- function create(id, blob, base64) {
- return {
- id: constant(id),
- blob: constant(blob),
- base64: constant(base64),
- blobUri: constant(URL.createObjectURL(blob))
- };
- }
- function add(blobInfo) {
- if (!get(blobInfo.id())) {
- cache.push(blobInfo);
- }
- }
- function get(id) {
- return findFirst(function(cachedBlobInfo) {
- return cachedBlobInfo.id() === id;
- });
- }
- function findFirst(predicate) {
- return Arr.filter(cache, predicate)[0];
- }
- function getByUri(blobUri) {
- return findFirst(function(blobInfo) {
- return blobInfo.blobUri() == blobUri;
- });
- }
- function removeByUri(blobUri) {
- cache = Arr.filter(cache, function(blobInfo) {
- if (blobInfo.blobUri() === blobUri) {
- URL.revokeObjectURL(blobInfo.blobUri());
- return false;
- }
- return true;
- });
- }
- function destroy() {
- Arr.each(cache, function(cachedBlobInfo) {
- URL.revokeObjectURL(cachedBlobInfo.blobUri());
- });
- cache = [];
- }
- return {
- create: create,
- add: add,
- get: get,
- getByUri: getByUri,
- findFirst: findFirst,
- removeByUri: removeByUri,
- destroy: destroy
- };
- };
- });
- // Included from: js/tinymce/classes/file/UploadStatus.js
- /**
- * UploadStatus.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Holds the current status of a blob uri, if it's pending or uploaded and what the result urls was.
- *
- * @private
- * @class tinymce.file.UploadStatus
- */
- define("tinymce/file/UploadStatus", [
- ], function() {
- return function() {
- var PENDING = 1, UPLOADED = 2;
- var blobUriStatuses = {};
- function createStatus(status, resultUri) {
- return {
- status: status,
- resultUri: resultUri
- };
- }
- function hasBlobUri(blobUri) {
- return blobUri in blobUriStatuses;
- }
- function getResultUri(blobUri) {
- var result = blobUriStatuses[blobUri];
- return result ? result.resultUri : null;
- }
- function isPending(blobUri) {
- return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false;
- }
- function isUploaded(blobUri) {
- return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false;
- }
- function markPending(blobUri) {
- blobUriStatuses[blobUri] = createStatus(PENDING, null);
- }
- function markUploaded(blobUri, resultUri) {
- blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri);
- }
- function removeFailed(blobUri) {
- delete blobUriStatuses[blobUri];
- }
- function destroy() {
- blobUriStatuses = {};
- }
- return {
- hasBlobUri: hasBlobUri,
- getResultUri: getResultUri,
- isPending: isPending,
- isUploaded: isUploaded,
- markPending: markPending,
- markUploaded: markUploaded,
- removeFailed: removeFailed,
- destroy: destroy
- };
- };
- });
- // Included from: js/tinymce/classes/EditorUpload.js
- /**
- * EditorUpload.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Handles image uploads, updates undo stack and patches over various internal functions.
- *
- * @private
- * @class tinymce.EditorUpload
- */
- define("tinymce/EditorUpload", [
- "tinymce/util/Arr",
- "tinymce/file/Uploader",
- "tinymce/file/ImageScanner",
- "tinymce/file/BlobCache",
- "tinymce/file/UploadStatus"
- ], function(Arr, Uploader, ImageScanner, BlobCache, UploadStatus) {
- return function(editor) {
- var blobCache = new BlobCache(), uploader, imageScanner, settings = editor.settings;
- var uploadStatus = new UploadStatus();
- function aliveGuard(callback) {
- return function(result) {
- if (editor.selection) {
- return callback(result);
- }
- return [];
- };
- }
- // Replaces strings without regexps to avoid FF regexp to big issue
- function replaceString(content, search, replace) {
- var index = 0;
- do {
- index = content.indexOf(search, index);
- if (index !== -1) {
- content = content.substring(0, index) + replace + content.substr(index + search.length);
- index += replace.length - search.length + 1;
- }
- } while (index !== -1);
- return content;
- }
- function replaceImageUrl(content, targetUrl, replacementUrl) {
- content = replaceString(content, 'src="' + targetUrl + '"', 'src="' + replacementUrl + '"');
- content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
- return content;
- }
- function replaceUrlInUndoStack(targetUrl, replacementUrl) {
- Arr.each(editor.undoManager.data, function(level) {
- level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
- });
- }
- function openNotification() {
- return editor.notificationManager.open({
- text: editor.translate('Image uploading...'),
- type: 'info',
- timeout: -1,
- progressBar: true
- });
- }
- function replaceImageUri(image, resultUri) {
- blobCache.removeByUri(image.src);
- replaceUrlInUndoStack(image.src, resultUri);
- editor.$(image).attr({
- src: resultUri,
- 'data-mce-src': editor.convertURL(resultUri, 'src')
- });
- }
- function uploadImages(callback) {
- if (!uploader) {
- uploader = new Uploader(uploadStatus, {
- url: settings.images_upload_url,
- basePath: settings.images_upload_base_path,
- credentials: settings.images_upload_credentials,
- handler: settings.images_upload_handler
- });
- }
- return scanForImages().then(aliveGuard(function(imageInfos) {
- var blobInfos;
- blobInfos = Arr.map(imageInfos, function(imageInfo) {
- return imageInfo.blobInfo;
- });
- return uploader.upload(blobInfos, openNotification).then(aliveGuard(function(result) {
- result = Arr.map(result, function(uploadInfo, index) {
- var image = imageInfos[index].image;
- if (uploadInfo.status && editor.settings.images_replace_blob_uris !== false) {
- replaceImageUri(image, uploadInfo.url);
- }
- return {
- element: image,
- status: uploadInfo.status
- };
- });
- if (callback) {
- callback(result);
- }
- return result;
- }));
- }));
- }
- function uploadImagesAuto(callback) {
- if (settings.automatic_uploads !== false) {
- return uploadImages(callback);
- }
- }
- function isValidDataUriImage(imgElm) {
- return settings.images_dataimg_filter ? settings.images_dataimg_filter(imgElm) : true;
- }
- function scanForImages() {
- if (!imageScanner) {
- imageScanner = new ImageScanner(uploadStatus, blobCache);
- }
- return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(function(result) {
- Arr.each(result, function(resultItem) {
- replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
- resultItem.image.src = resultItem.blobInfo.blobUri();
- resultItem.image.removeAttribute('data-mce-src');
- });
- return result;
- }));
- }
- function destroy() {
- blobCache.destroy();
- uploadStatus.destroy();
- imageScanner = uploader = null;
- }
- function replaceBlobUris(content) {
- return content.replace(/src="(blob:[^"]+)"/g, function(match, blobUri) {
- var resultUri = uploadStatus.getResultUri(blobUri);
- if (resultUri) {
- return 'src="' + resultUri + '"';
- }
- var blobInfo = blobCache.getByUri(blobUri);
- if (!blobInfo) {
- blobInfo = Arr.reduce(editor.editorManager.editors, function(result, editor) {
- return result || editor.editorUpload.blobCache.getByUri(blobUri);
- }, null);
- }
- if (blobInfo) {
- return 'src="data:' + blobInfo.blob().type + ';base64,' + blobInfo.base64() + '"';
- }
- return match;
- });
- }
- editor.on('setContent', function() {
- if (editor.settings.automatic_uploads !== false) {
- uploadImagesAuto();
- } else {
- scanForImages();
- }
- });
- editor.on('RawSaveContent', function(e) {
- e.content = replaceBlobUris(e.content);
- });
- editor.on('getContent', function(e) {
- if (e.source_view || e.format == 'raw') {
- return;
- }
- e.content = replaceBlobUris(e.content);
- });
- editor.on('PostRender', function() {
- editor.parser.addNodeFilter('img', function(images) {
- Arr.each(images, function(img) {
- var src = img.attr('src');
- if (blobCache.getByUri(src)) {
- return;
- }
- var resultUri = uploadStatus.getResultUri(src);
- if (resultUri) {
- img.attr('src', resultUri);
- }
- });
- });
- });
- return {
- blobCache: blobCache,
- uploadImages: uploadImages,
- uploadImagesAuto: uploadImagesAuto,
- scanForImages: scanForImages,
- destroy: destroy
- };
- };
- });
- // Included from: js/tinymce/classes/caret/FakeCaret.js
- /**
- * FakeCaret.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module contains logic for rendering a fake visual caret.
- *
- * @private
- * @class tinymce.caret.FakeCaret
- */
- define("tinymce/caret/FakeCaret", [
- "tinymce/caret/CaretContainer",
- "tinymce/caret/CaretPosition",
- "tinymce/dom/NodeType",
- "tinymce/dom/RangeUtils",
- "tinymce/dom/DomQuery",
- "tinymce/geom/ClientRect",
- "tinymce/util/Delay"
- ], function(CaretContainer, CaretPosition, NodeType, RangeUtils, $, ClientRect, Delay) {
- var isContentEditableFalse = NodeType.isContentEditableFalse;
- return function(rootNode, isBlock) {
- var cursorInterval, $lastVisualCaret, caretContainerNode;
- function getAbsoluteClientRect(node, before) {
- var clientRect = ClientRect.collapse(node.getBoundingClientRect(), before),
- docElm, scrollX, scrollY, margin, rootRect;
- if (rootNode.tagName == 'BODY') {
- docElm = rootNode.ownerDocument.documentElement;
- scrollX = rootNode.scrollLeft || docElm.scrollLeft;
- scrollY = rootNode.scrollTop || docElm.scrollTop;
- } else {
- rootRect = rootNode.getBoundingClientRect();
- scrollX = rootNode.scrollLeft - rootRect.left;
- scrollY = rootNode.scrollTop - rootRect.top;
- }
- clientRect.left += scrollX;
- clientRect.right += scrollX;
- clientRect.top += scrollY;
- clientRect.bottom += scrollY;
- clientRect.width = 1;
- margin = node.offsetWidth - node.clientWidth;
- if (margin > 0) {
- if (before) {
- margin *= -1;
- }
- clientRect.left += margin;
- clientRect.right += margin;
- }
- return clientRect;
- }
- function trimInlineCaretContainers() {
- var contentEditableFalseNodes, node, sibling, i, data;
- contentEditableFalseNodes = $('*[contentEditable=false]', rootNode);
- for (i = 0; i < contentEditableFalseNodes.length; i++) {
- node = contentEditableFalseNodes[i];
- sibling = node.previousSibling;
- if (CaretContainer.endsWithCaretContainer(sibling)) {
- data = sibling.data;
- if (data.length == 1) {
- sibling.parentNode.removeChild(sibling);
- } else {
- sibling.deleteData(data.length - 1, 1);
- }
- }
- sibling = node.nextSibling;
- if (CaretContainer.startsWithCaretContainer(sibling)) {
- data = sibling.data;
- if (data.length == 1) {
- sibling.parentNode.removeChild(sibling);
- } else {
- sibling.deleteData(0, 1);
- }
- }
- }
- return null;
- }
- function show(before, node) {
- var clientRect, rng, container;
- hide();
- if (isBlock(node)) {
- caretContainerNode = CaretContainer.insertBlock('p', node, before);
- clientRect = getAbsoluteClientRect(node, before);
- $(caretContainerNode).css('top', clientRect.top);
- $lastVisualCaret = $('<div class="mce-visual-caret" data-mce-bogus="all"></div>').css(clientRect).appendTo(rootNode);
- if (before) {
- $lastVisualCaret.addClass('mce-visual-caret-before');
- }
- startBlink();
- rng = node.ownerDocument.createRange();
- container = caretContainerNode.firstChild;
- rng.setStart(container, 0);
- rng.setEnd(container, 1);
- } else {
- caretContainerNode = CaretContainer.insertInline(node, before);
- rng = node.ownerDocument.createRange();
- if (isContentEditableFalse(caretContainerNode.nextSibling)) {
- rng.setStart(caretContainerNode, 0);
- rng.setEnd(caretContainerNode, 0);
- } else {
- rng.setStart(caretContainerNode, 1);
- rng.setEnd(caretContainerNode, 1);
- }
- return rng;
- }
- return rng;
- }
- function hide() {
- trimInlineCaretContainers();
- if (caretContainerNode) {
- CaretContainer.remove(caretContainerNode);
- caretContainerNode = null;
- }
- if ($lastVisualCaret) {
- $lastVisualCaret.remove();
- $lastVisualCaret = null;
- }
- clearInterval(cursorInterval);
- }
- function startBlink() {
- cursorInterval = Delay.setInterval(function() {
- $('div.mce-visual-caret', rootNode).toggleClass('mce-visual-caret-hidden');
- }, 500);
- }
- function destroy() {
- Delay.clearInterval(cursorInterval);
- }
- function getCss() {
- return (
- '.mce-visual-caret {' +
- 'position: absolute;' +
- 'background-color: black;' +
- 'background-color: currentcolor;' +
- '}' +
- '.mce-visual-caret-hidden {' +
- 'display: none;' +
- '}' +
- '*[data-mce-caret] {' +
- 'position: absolute;' +
- 'left: -1000px;' +
- 'right: auto;' +
- 'top: 0;' +
- 'margin: 0;' +
- 'padding: 0;' +
- '}'
- );
- }
- return {
- show: show,
- hide: hide,
- getCss: getCss,
- destroy: destroy
- };
- };
- });
- // Included from: js/tinymce/classes/dom/Dimensions.js
- /**
- * Dimensions.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module measures nodes and returns client rects. The client rects has an
- * extra node property.
- *
- * @private
- * @class tinymce.dom.Dimensions
- */
- define("tinymce/dom/Dimensions", [
- "tinymce/util/Arr",
- "tinymce/dom/NodeType",
- "tinymce/geom/ClientRect"
- ], function(Arr, NodeType, ClientRect) {
- function getClientRects(node) {
- function toArrayWithNode(clientRects) {
- return Arr.map(clientRects, function(clientRect) {
- clientRect = ClientRect.clone(clientRect);
- clientRect.node = node;
- return clientRect;
- });
- }
- if (Arr.isArray(node)) {
- return Arr.reduce(node, function(result, node) {
- return result.concat(getClientRects(node));
- }, []);
- }
- if (NodeType.isElement(node)) {
- return toArrayWithNode(node.getClientRects());
- }
- if (NodeType.isText(node)) {
- var rng = node.ownerDocument.createRange();
- rng.setStart(node, 0);
- rng.setEnd(node, node.data.length);
- return toArrayWithNode(rng.getClientRects());
- }
- }
- return {
- /**
- * Returns the client rects for a specific node.
- *
- * @method getClientRects
- * @param {Array/DOMNode} node Node or array of nodes to get client rects on.
- * @param {Array} Array of client rects with a extra node property.
- */
- getClientRects: getClientRects
- };
- });
- // Included from: js/tinymce/classes/caret/LineWalker.js
- /**
- * LineWalker.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module lets you walk the document line by line
- * returing nodes and client rects for each line.
- *
- * @private
- * @class tinymce.caret.LineWalker
- */
- define("tinymce/caret/LineWalker", [
- "tinymce/util/Fun",
- "tinymce/util/Arr",
- "tinymce/dom/Dimensions",
- "tinymce/caret/CaretCandidate",
- "tinymce/caret/CaretUtils",
- "tinymce/caret/CaretWalker",
- "tinymce/caret/CaretPosition",
- "tinymce/geom/ClientRect"
- ], function(Fun, Arr, Dimensions, CaretCandidate, CaretUtils, CaretWalker, CaretPosition, ClientRect) {
- var curry = Fun.curry;
- function findUntil(direction, rootNode, predicateFn, node) {
- while ((node = CaretUtils.findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
- if (predicateFn(node)) {
- return;
- }
- }
- }
- function walkUntil(direction, isAboveFn, isBeflowFn, rootNode, predicateFn, caretPosition) {
- var line = 0, node, result = [], targetClientRect;
- function add(node) {
- var i, clientRect, clientRects;
- clientRects = Dimensions.getClientRects(node);
- if (direction == -1) {
- clientRects = clientRects.reverse();
- }
- for (i = 0; i < clientRects.length; i++) {
- clientRect = clientRects[i];
- if (isBeflowFn(clientRect, targetClientRect)) {
- continue;
- }
- if (result.length > 0 && isAboveFn(clientRect, Arr.last(result))) {
- line++;
- }
- clientRect.line = line;
- if (predicateFn(clientRect)) {
- return true;
- }
- result.push(clientRect);
- }
- }
- targetClientRect = Arr.last(caretPosition.getClientRects());
- if (!targetClientRect) {
- return result;
- }
- node = caretPosition.getNode();
- add(node);
- findUntil(direction, rootNode, add, node);
- return result;
- }
- function aboveLineNumber(lineNumber, clientRect) {
- return clientRect.line > lineNumber;
- }
- function isLine(lineNumber, clientRect) {
- return clientRect.line === lineNumber;
- }
- var upUntil = curry(walkUntil, -1, ClientRect.isAbove, ClientRect.isBelow);
- var downUntil = curry(walkUntil, 1, ClientRect.isBelow, ClientRect.isAbove);
- function positionsUntil(direction, rootNode, predicateFn, node) {
- var caretWalker = new CaretWalker(rootNode), walkFn, isBelowFn, isAboveFn,
- caretPosition, result = [], line = 0, clientRect, targetClientRect;
- function getClientRect(caretPosition) {
- if (direction == 1) {
- return Arr.last(caretPosition.getClientRects());
- }
- return Arr.last(caretPosition.getClientRects());
- }
- if (direction == 1) {
- walkFn = caretWalker.next;
- isBelowFn = ClientRect.isBelow;
- isAboveFn = ClientRect.isAbove;
- caretPosition = CaretPosition.after(node);
- } else {
- walkFn = caretWalker.prev;
- isBelowFn = ClientRect.isAbove;
- isAboveFn = ClientRect.isBelow;
- caretPosition = CaretPosition.before(node);
- }
- targetClientRect = getClientRect(caretPosition);
- do {
- if (!caretPosition.isVisible()) {
- continue;
- }
- clientRect = getClientRect(caretPosition);
- if (isAboveFn(clientRect, targetClientRect)) {
- continue;
- }
- if (result.length > 0 && isBelowFn(clientRect, Arr.last(result))) {
- line++;
- }
- clientRect = ClientRect.clone(clientRect);
- clientRect.position = caretPosition;
- clientRect.line = line;
- if (predicateFn(clientRect)) {
- return result;
- }
- result.push(clientRect);
- } while ((caretPosition = walkFn(caretPosition)));
- return result;
- }
- return {
- upUntil: upUntil,
- downUntil: downUntil,
- /**
- * Find client rects with line and caret position until the predicate returns true.
- *
- * @method positionsUntil
- * @param {Number} direction Direction forward/backward 1/-1.
- * @param {DOMNode} rootNode Root node to walk within.
- * @param {function} predicateFn Gets the client rect as it's input.
- * @param {DOMNode} node Node to start walking from.
- * @return {Array} Array of client rects with line and position properties.
- */
- positionsUntil: positionsUntil,
- isAboveLine: curry(aboveLineNumber),
- isLine: curry(isLine)
- };
- });
- // Included from: js/tinymce/classes/caret/LineUtils.js
- /**
- * LineUtils.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Utility functions for working with lines.
- *
- * @private
- * @class tinymce.caret.LineUtils
- */
- define("tinymce/caret/LineUtils", [
- "tinymce/util/Fun",
- "tinymce/util/Arr",
- "tinymce/dom/NodeType",
- "tinymce/dom/Dimensions",
- "tinymce/geom/ClientRect",
- "tinymce/caret/CaretUtils",
- "tinymce/caret/CaretCandidate"
- ], function(Fun, Arr, NodeType, Dimensions, ClientRect, CaretUtils, CaretCandidate) {
- var isContentEditableFalse = NodeType.isContentEditableFalse,
- findNode = CaretUtils.findNode,
- curry = Fun.curry;
- function distanceToRectLeft(clientRect, clientX) {
- return Math.abs(clientRect.left - clientX);
- }
- function distanceToRectRight(clientRect, clientX) {
- return Math.abs(clientRect.right - clientX);
- }
- function findClosestClientRect(clientRects, clientX) {
- function isInside(clientX, clientRect) {
- return clientX >= clientRect.left && clientX <= clientRect.right;
- }
- return Arr.reduce(clientRects, function(oldClientRect, clientRect) {
- var oldDistance, newDistance;
- oldDistance = Math.min(distanceToRectLeft(oldClientRect, clientX), distanceToRectRight(oldClientRect, clientX));
- newDistance = Math.min(distanceToRectLeft(clientRect, clientX), distanceToRectRight(clientRect, clientX));
- if (isInside(clientX, clientRect)) {
- return clientRect;
- }
- if (isInside(clientX, oldClientRect)) {
- return oldClientRect;
- }
- // cE=false has higher priority
- if (newDistance == oldDistance && isContentEditableFalse(clientRect.node)) {
- return clientRect;
- }
- if (newDistance < oldDistance) {
- return clientRect;
- }
- return oldClientRect;
- });
- }
- function walkUntil(direction, rootNode, predicateFn, node) {
- while ((node = findNode(node, direction, CaretCandidate.isEditableCaretCandidate, rootNode))) {
- if (predicateFn(node)) {
- return;
- }
- }
- }
- function findLineNodeRects(rootNode, targetNodeRect) {
- var clientRects = [];
- function collect(checkPosFn, node) {
- var lineRects;
- lineRects = Arr.filter(Dimensions.getClientRects(node), function(clientRect) {
- return !checkPosFn(clientRect, targetNodeRect);
- });
- clientRects = clientRects.concat(lineRects);
- return lineRects.length === 0;
- }
- clientRects.push(targetNodeRect);
- walkUntil(-1, rootNode, curry(collect, ClientRect.isAbove), targetNodeRect.node);
- walkUntil(1, rootNode, curry(collect, ClientRect.isBelow), targetNodeRect.node);
- return clientRects;
- }
- function getContentEditableFalseChildren(rootNode) {
- return Arr.filter(Arr.toArray(rootNode.getElementsByTagName('*')), isContentEditableFalse);
- }
- function caretInfo(clientRect, clientX) {
- return {
- node: clientRect.node,
- before: distanceToRectLeft(clientRect, clientX) < distanceToRectRight(clientRect, clientX)
- };
- }
- function closestCaret(rootNode, clientX, clientY) {
- var contentEditableFalseNodeRects, closestNodeRect;
- contentEditableFalseNodeRects = Dimensions.getClientRects(getContentEditableFalseChildren(rootNode));
- contentEditableFalseNodeRects = Arr.filter(contentEditableFalseNodeRects, function(clientRect) {
- return clientY >= clientRect.top && clientY <= clientRect.bottom;
- });
- closestNodeRect = findClosestClientRect(contentEditableFalseNodeRects, clientX);
- if (closestNodeRect) {
- closestNodeRect = findClosestClientRect(findLineNodeRects(rootNode, closestNodeRect), clientX);
- if (closestNodeRect && isContentEditableFalse(closestNodeRect.node)) {
- return caretInfo(closestNodeRect, clientX);
- }
- }
- return null;
- }
- return {
- findClosestClientRect: findClosestClientRect,
- findLineNodeRects: findLineNodeRects,
- closestCaret: closestCaret
- };
- });
- // Included from: js/tinymce/classes/DragDropOverrides.js
- /**
- * DragDropOverrides.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module contains logic overriding the drag/drop logic of the editor.
- *
- * @private
- * @class tinymce.DragDropOverrides
- */
- define("tinymce/DragDropOverrides", [
- "tinymce/dom/NodeType",
- "tinymce/util/Arr",
- "tinymce/util/Fun"
- ], function(
- NodeType,
- Arr,
- Fun
- ) {
- var isContentEditableFalse = NodeType.isContentEditableFalse,
- isContentEditableTrue = NodeType.isContentEditableTrue;
- function init(editor) {
- var $ = editor.$, rootDocument = document,
- editableDoc = editor.getDoc(),
- dom = editor.dom, state = {};
- function isDraggable(elm) {
- return isContentEditableFalse(elm);
- }
- function setBodyCursor(cursor) {
- $(editor.getBody()).css('cursor', cursor);
- }
- function isValidDropTarget(elm) {
- if (elm == state.element || editor.dom.isChildOf(elm, state.element)) {
- return false;
- }
- if (isContentEditableFalse(elm)) {
- return false;
- }
- return true;
- }
- function move(e) {
- var deltaX, deltaY, pos, viewPort,
- overflowX = 0, overflowY = 0, movement,
- clientX, clientY, rootClientRect;
- if (e.button !== 0) {
- return;
- }
- deltaX = e.screenX - state.screenX;
- deltaY = e.screenY - state.screenY;
- movement = Math.max(Math.abs(deltaX), Math.abs(deltaY));
- if (!state.dragging && movement > 10) {
- state.dragging = true;
- setBodyCursor('default');
- state.clone = state.element.cloneNode(true);
- pos = dom.getPos(state.element);
- state.relX = state.clientX - pos.x;
- state.relY = state.clientY - pos.y;
- state.width = state.element.offsetWidth;
- state.height = state.element.offsetHeight;
- $(state.clone).css({
- width: state.width,
- height: state.height
- }).removeAttr('data-mce-selected');
- state.ghost = $('<div>').css({
- position: 'absolute',
- opacity: 0.5,
- overflow: 'hidden',
- width: state.width,
- height: state.height
- }).attr({
- 'data-mce-bogus': 'all',
- unselectable: 'on',
- contenteditable: 'false'
- }).addClass('mce-drag-container mce-reset').
- append(state.clone).
- appendTo(editor.getBody())[0];
- viewPort = editor.dom.getViewPort(editor.getWin());
- state.maxX = viewPort.w;
- state.maxY = viewPort.h;
- }
- if (state.dragging) {
- editor._selectionOverrides.hideFakeCaret();
- editor.selection.placeCaretAt(e.clientX, e.clientY);
- clientX = state.clientX + deltaX - state.relX;
- clientY = state.clientY + deltaY + 5;
- if (clientX + state.width > state.maxX) {
- overflowX = (clientX + state.width) - state.maxX;
- }
- if (clientY + state.height > state.maxY) {
- overflowY = (clientY + state.height) - state.maxY;
- }
- if (editor.getBody().nodeName != 'BODY') {
- rootClientRect = editor.getBody().getBoundingClientRect();
- } else {
- rootClientRect = {left: 0, top: 0};
- }
- $(state.ghost).css({
- left: clientX - rootClientRect.left,
- top: clientY - rootClientRect.top,
- width: state.width - overflowX,
- height: state.height - overflowY
- });
- }
- }
- function drop(evt) {
- var dropEvt;
- if (state.dragging) {
- // Hack for IE since it doesn't sync W3C Range with IE Specific range
- editor.selection.setRng(editor.selection.getSel().getRangeAt(0));
- if (isValidDropTarget(editor.selection.getNode())) {
- var targetClone = state.element;
- // Pass along clientX, clientY if we have them
- dropEvt = editor.fire('drop', {
- targetClone: targetClone,
- clientX: evt.clientX,
- clientY: evt.clientY
- });
- if (dropEvt.isDefaultPrevented()) {
- return;
- }
- targetClone = dropEvt.targetClone;
- editor.undoManager.transact(function() {
- editor.insertContent(dom.getOuterHTML(targetClone));
- $(state.element).remove();
- });
- }
- }
- stop();
- }
- function start(e) {
- var ceElm, evt;
- stop();
- if (e.button !== 0) {
- return;
- }
- ceElm = Arr.find(editor.dom.getParents(e.target), Fun.or(isContentEditableFalse, isContentEditableTrue));
- if (isDraggable(ceElm)) {
- evt = editor.fire('dragstart', {target: ceElm});
- if (evt.isDefaultPrevented()) {
- return;
- }
- editor.on('mousemove', move);
- editor.on('mouseup', drop);
- if (rootDocument != editableDoc) {
- dom.bind(rootDocument, 'mousemove', move);
- dom.bind(rootDocument, 'mouseup', drop);
- }
- state = {
- screenX: e.screenX,
- screenY: e.screenY,
- clientX: e.clientX,
- clientY: e.clientY,
- element: ceElm
- };
- }
- }
- function stop() {
- $(state.ghost).remove();
- setBodyCursor(null);
- editor.off('mousemove', move);
- editor.off('mouseup', stop);
- if (rootDocument != editableDoc) {
- dom.unbind(rootDocument, 'mousemove', move);
- dom.unbind(rootDocument, 'mouseup', stop);
- }
- state = {};
- }
- editor.on('mousedown', start);
- // Blocks drop inside cE=false on IE
- editor.on('drop', function(e) {
- // FF doesn't pass out clientX/clientY for drop since this is for IE we just use null instead
- var realTarget = typeof e.clientX !== 'undefined' ? editor.getDoc().elementFromPoint(e.clientX, e.clientY) : null;
- if (isContentEditableFalse(realTarget) || isContentEditableFalse(editor.dom.getContentEditableParent(realTarget))) {
- e.preventDefault();
- }
- });
- }
- return {
- init: init
- };
- });
- // Included from: js/tinymce/classes/SelectionOverrides.js
- /**
- * SelectionOverrides.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This module contains logic overriding the selection with keyboard/mouse
- * around contentEditable=false regions.
- *
- * @example
- * // Disable the default cE=false selection
- * tinymce.activeEditor.on('ShowCaret BeforeObjectSelected', function(e) {
- * e.preventDefault();
- * });
- *
- * @private
- * @class tinymce.SelectionOverrides
- */
- define("tinymce/SelectionOverrides", [
- "tinymce/Env",
- "tinymce/caret/CaretWalker",
- "tinymce/caret/CaretPosition",
- "tinymce/caret/CaretContainer",
- "tinymce/caret/CaretUtils",
- "tinymce/caret/FakeCaret",
- "tinymce/caret/LineWalker",
- "tinymce/caret/LineUtils",
- "tinymce/dom/NodeType",
- "tinymce/dom/RangeUtils",
- "tinymce/geom/ClientRect",
- "tinymce/util/VK",
- "tinymce/util/Fun",
- "tinymce/util/Arr",
- "tinymce/util/Delay",
- "tinymce/DragDropOverrides",
- "tinymce/text/Zwsp"
- ], function(
- Env, CaretWalker, CaretPosition, CaretContainer, CaretUtils, FakeCaret, LineWalker,
- LineUtils, NodeType, RangeUtils, ClientRect, VK, Fun, Arr, Delay, DragDropOverrides, Zwsp
- ) {
- var curry = Fun.curry,
- isContentEditableTrue = NodeType.isContentEditableTrue,
- isContentEditableFalse = NodeType.isContentEditableFalse,
- isElement = NodeType.isElement,
- isAfterContentEditableFalse = CaretUtils.isAfterContentEditableFalse,
- isBeforeContentEditableFalse = CaretUtils.isBeforeContentEditableFalse,
- getSelectedNode = RangeUtils.getSelectedNode;
- function getVisualCaretPosition(walkFn, caretPosition) {
- while ((caretPosition = walkFn(caretPosition))) {
- if (caretPosition.isVisible()) {
- return caretPosition;
- }
- }
- return caretPosition;
- }
- function SelectionOverrides(editor) {
- var rootNode = editor.getBody(), caretWalker = new CaretWalker(rootNode);
- var getNextVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.next);
- var getPrevVisualCaretPosition = curry(getVisualCaretPosition, caretWalker.prev),
- fakeCaret = new FakeCaret(editor.getBody(), isBlock),
- realSelectionId = 'sel-' + editor.dom.uniqueId(),
- selectedContentEditableNode, $ = editor.$;
- function isBlock(node) {
- return editor.dom.isBlock(node);
- }
- function setRange(range) {
- //console.log('setRange', range);
- if (range) {
- editor.selection.setRng(range);
- }
- }
- function getRange() {
- return editor.selection.getRng();
- }
- function scrollIntoView(node, alignToTop) {
- editor.selection.scrollIntoView(node, alignToTop);
- }
- function showCaret(direction, node, before) {
- var e;
- e = editor.fire('ShowCaret', {
- target: node,
- direction: direction,
- before: before
- });
- if (e.isDefaultPrevented()) {
- return null;
- }
- scrollIntoView(node, direction === -1);
- return fakeCaret.show(before, node);
- }
- function selectNode(node) {
- var e;
- fakeCaret.hide();
- e = editor.fire('BeforeObjectSelected', {target: node});
- if (e.isDefaultPrevented()) {
- return null;
- }
- return getNodeRange(node);
- }
- function getNodeRange(node) {
- var rng = node.ownerDocument.createRange();
- rng.selectNode(node);
- return rng;
- }
- function isMoveInsideSameBlock(fromCaretPosition, toCaretPosition) {
- var inSameBlock = CaretUtils.isInSameBlock(fromCaretPosition, toCaretPosition);
- // Handle bogus BR <p>abc|<br></p>
- if (!inSameBlock && NodeType.isBr(fromCaretPosition.getNode())) {
- return true;
- }
- return inSameBlock;
- }
- function getNormalizedRangeEndPoint(direction, range) {
- range = CaretUtils.normalizeRange(direction, rootNode, range);
- if (direction == -1) {
- return CaretPosition.fromRangeStart(range);
- }
- return CaretPosition.fromRangeEnd(range);
- }
- function isRangeInCaretContainerBlock(range) {
- return CaretContainer.isCaretContainerBlock(range.startContainer);
- }
- function moveToCeFalseHorizontally(direction, getNextPosFn, isBeforeContentEditableFalseFn, range) {
- var node, caretPosition, peekCaretPosition, rangeIsInContainerBlock;
- if (!range.collapsed) {
- node = getSelectedNode(range);
- if (isContentEditableFalse(node)) {
- return showCaret(direction, node, direction == -1);
- }
- }
- rangeIsInContainerBlock = isRangeInCaretContainerBlock(range);
- caretPosition = getNormalizedRangeEndPoint(direction, range);
- if (isBeforeContentEditableFalseFn(caretPosition)) {
- return selectNode(caretPosition.getNode(direction == -1));
- }
- caretPosition = getNextPosFn(caretPosition);
- if (!caretPosition) {
- if (rangeIsInContainerBlock) {
- return range;
- }
- return null;
- }
- if (isBeforeContentEditableFalseFn(caretPosition)) {
- return showCaret(direction, caretPosition.getNode(direction == -1), direction == 1);
- }
- // Peek ahead for handling of ab|c<span cE=false> -> abc|<span cE=false>
- peekCaretPosition = getNextPosFn(caretPosition);
- if (isBeforeContentEditableFalseFn(peekCaretPosition)) {
- if (isMoveInsideSameBlock(caretPosition, peekCaretPosition)) {
- return showCaret(direction, peekCaretPosition.getNode(direction == -1), direction == 1);
- }
- }
- if (rangeIsInContainerBlock) {
- return renderRangeCaret(caretPosition.toRange());
- }
- return null;
- }
- function moveToCeFalseVertically(direction, walkerFn, range) {
- var caretPosition, linePositions, nextLinePositions,
- closestNextLineRect, caretClientRect, clientX,
- dist1, dist2, contentEditableFalseNode;
- contentEditableFalseNode = getSelectedNode(range);
- caretPosition = getNormalizedRangeEndPoint(direction, range);
- linePositions = walkerFn(rootNode, LineWalker.isAboveLine(1), caretPosition);
- nextLinePositions = Arr.filter(linePositions, LineWalker.isLine(1));
- caretClientRect = Arr.last(caretPosition.getClientRects());
- if (isBeforeContentEditableFalse(caretPosition)) {
- contentEditableFalseNode = caretPosition.getNode();
- }
- if (isAfterContentEditableFalse(caretPosition)) {
- contentEditableFalseNode = caretPosition.getNode(true);
- }
- if (!caretClientRect) {
- return null;
- }
- clientX = caretClientRect.left;
- closestNextLineRect = LineUtils.findClosestClientRect(nextLinePositions, clientX);
- if (closestNextLineRect) {
- if (isContentEditableFalse(closestNextLineRect.node)) {
- dist1 = Math.abs(clientX - closestNextLineRect.left);
- dist2 = Math.abs(clientX - closestNextLineRect.right);
- return showCaret(direction, closestNextLineRect.node, dist1 < dist2);
- }
- }
- if (contentEditableFalseNode) {
- var caretPositions = LineWalker.positionsUntil(direction, rootNode, LineWalker.isAboveLine(1), contentEditableFalseNode);
- closestNextLineRect = LineUtils.findClosestClientRect(Arr.filter(caretPositions, LineWalker.isLine(1)), clientX);
- if (closestNextLineRect) {
- return renderRangeCaret(closestNextLineRect.position.toRange());
- }
- closestNextLineRect = Arr.last(Arr.filter(caretPositions, LineWalker.isLine(0)));
- if (closestNextLineRect) {
- return renderRangeCaret(closestNextLineRect.position.toRange());
- }
- }
- }
- function exitPreBlock(direction, range) {
- var pre, caretPos, newBlock;
- function createTextBlock() {
- var textBlock = editor.dom.create(editor.settings.forced_root_block);
- if (!Env.ie || Env.ie >= 11) {
- textBlock.innerHTML = '<br data-mce-bogus="1">';
- }
- return textBlock;
- }
- if (range.collapsed && editor.settings.forced_root_block) {
- pre = editor.dom.getParent(range.startContainer, 'PRE');
- if (!pre) {
- return;
- }
- if (direction == 1) {
- caretPos = getNextVisualCaretPosition(CaretPosition.fromRangeStart(range));
- } else {
- caretPos = getPrevVisualCaretPosition(CaretPosition.fromRangeStart(range));
- }
- if (!caretPos) {
- newBlock = createTextBlock();
- if (direction == 1) {
- editor.$(pre).after(newBlock);
- } else {
- editor.$(pre).before(newBlock);
- }
- editor.selection.select(newBlock, true);
- editor.selection.collapse();
- }
- }
- }
- function moveH(direction, getNextPosFn, isBeforeContentEditableFalseFn, range) {
- var newRange;
- newRange = moveToCeFalseHorizontally(direction, getNextPosFn, isBeforeContentEditableFalseFn, range);
- if (newRange) {
- return newRange;
- }
- newRange = exitPreBlock(direction, range);
- if (newRange) {
- return newRange;
- }
- return null;
- }
- function moveV(direction, walkerFn, range) {
- var newRange;
- newRange = moveToCeFalseVertically(direction, walkerFn, range);
- if (newRange) {
- return newRange;
- }
- newRange = exitPreBlock(direction, range);
- if (newRange) {
- return newRange;
- }
- return null;
- }
- function getBlockCaretContainer() {
- return $('*[data-mce-caret]')[0];
- }
- function showBlockCaretContainer(blockCaretContainer) {
- blockCaretContainer = $(blockCaretContainer);
- if (blockCaretContainer.attr('data-mce-caret')) {
- fakeCaret.hide();
- blockCaretContainer.removeAttr('data-mce-caret');
- blockCaretContainer.removeAttr('data-mce-bogus');
- blockCaretContainer.removeAttr('style');
- // Removes control rect on IE
- setRange(getRange());
- scrollIntoView(blockCaretContainer[0]);
- }
- }
- function renderCaretAtRange(range) {
- var caretPosition, ceRoot;
- range = CaretUtils.normalizeRange(1, rootNode, range);
- caretPosition = CaretPosition.fromRangeStart(range);
- if (isContentEditableFalse(caretPosition.getNode())) {
- return showCaret(1, caretPosition.getNode(), !caretPosition.isAtEnd());
- }
- if (isContentEditableFalse(caretPosition.getNode(true))) {
- return showCaret(1, caretPosition.getNode(true), false);
- }
- // TODO: Should render caret before/after depending on where you click on the page forces after now
- ceRoot = editor.dom.getParent(caretPosition.getNode(), Fun.or(isContentEditableFalse, isContentEditableTrue));
- if (isContentEditableFalse(ceRoot)) {
- return showCaret(1, ceRoot, false);
- }
- fakeCaret.hide();
- return null;
- }
- function renderRangeCaret(range) {
- var caretRange;
- if (!range || !range.collapsed) {
- return range;
- }
- caretRange = renderCaretAtRange(range);
- if (caretRange) {
- return caretRange;
- }
- return range;
- }
- function deleteContentEditableNode(node) {
- var nextCaretPosition, prevCaretPosition, prevCeFalseElm, nextElement;
- if (!isContentEditableFalse(node)) {
- return null;
- }
- if (isContentEditableFalse(node.previousSibling)) {
- prevCeFalseElm = node.previousSibling;
- }
- prevCaretPosition = getPrevVisualCaretPosition(CaretPosition.before(node));
- if (!prevCaretPosition) {
- nextCaretPosition = getNextVisualCaretPosition(CaretPosition.after(node));
- }
- if (nextCaretPosition && isElement(nextCaretPosition.getNode())) {
- nextElement = nextCaretPosition.getNode();
- }
- CaretContainer.remove(node.previousSibling);
- CaretContainer.remove(node.nextSibling);
- editor.dom.remove(node);
- clearContentEditableSelection();
- if (editor.dom.isEmpty(editor.getBody())) {
- editor.setContent('');
- editor.focus();
- return;
- }
- if (prevCeFalseElm) {
- return CaretPosition.after(prevCeFalseElm).toRange();
- }
- if (nextElement) {
- return CaretPosition.before(nextElement).toRange();
- }
- if (prevCaretPosition) {
- return prevCaretPosition.toRange();
- }
- if (nextCaretPosition) {
- return nextCaretPosition.toRange();
- }
- return null;
- }
- function mergeTextBlocks(direction, fromCaretPosition, toCaretPosition) {
- var dom = editor.dom, fromBlock, toBlock, node, textBlocks;
- if (direction === -1) {
- if (isAfterContentEditableFalse(toCaretPosition) && isBlock(toCaretPosition.getNode(true))) {
- return deleteContentEditableNode(toCaretPosition.getNode(true));
- }
- } else {
- if (isBeforeContentEditableFalse(fromCaretPosition) && isBlock(fromCaretPosition.getNode())) {
- return deleteContentEditableNode(fromCaretPosition.getNode());
- }
- }
- textBlocks = editor.schema.getTextBlockElements();
- fromBlock = dom.getParent(fromCaretPosition.getNode(), dom.isBlock);
- toBlock = dom.getParent(toCaretPosition.getNode(), dom.isBlock);
- // Verify that both blocks are text blocks
- if (fromBlock === toBlock || !textBlocks[fromBlock.nodeName] || !textBlocks[toBlock.nodeName]) {
- return null;
- }
- while ((node = fromBlock.firstChild)) {
- toBlock.appendChild(node);
- }
- editor.dom.remove(fromBlock);
- return toCaretPosition.toRange();
- }
- function backspaceDelete(direction, beforeFn, afterFn, range) {
- var node, caretPosition, peekCaretPosition, newCaretPosition;
- if (!range.collapsed) {
- node = getSelectedNode(range);
- if (isContentEditableFalse(node)) {
- return renderRangeCaret(deleteContentEditableNode(node));
- }
- }
- caretPosition = getNormalizedRangeEndPoint(direction, range);
- if (afterFn(caretPosition) && CaretContainer.isCaretContainerBlock(range.startContainer)) {
- newCaretPosition = direction == -1 ? caretWalker.prev(caretPosition) : caretWalker.next(caretPosition);
- return newCaretPosition ? renderRangeCaret(newCaretPosition.toRange()) : range;
- }
- if (beforeFn(caretPosition)) {
- return renderRangeCaret(deleteContentEditableNode(caretPosition.getNode(direction == -1)));
- }
- peekCaretPosition = direction == -1 ? caretWalker.prev(caretPosition) : caretWalker.next(caretPosition);
- if (beforeFn(peekCaretPosition)) {
- if (direction === -1) {
- return mergeTextBlocks(direction, caretPosition, peekCaretPosition);
- }
- return mergeTextBlocks(direction, peekCaretPosition, caretPosition);
- }
- }
- function registerEvents() {
- var right = curry(moveH, 1, getNextVisualCaretPosition, isBeforeContentEditableFalse);
- var left = curry(moveH, -1, getPrevVisualCaretPosition, isAfterContentEditableFalse);
- var deleteForward = curry(backspaceDelete, 1, isBeforeContentEditableFalse, isAfterContentEditableFalse);
- var backspace = curry(backspaceDelete, -1, isAfterContentEditableFalse, isBeforeContentEditableFalse);
- var up = curry(moveV, -1, LineWalker.upUntil);
- var down = curry(moveV, 1, LineWalker.downUntil);
- function override(evt, moveFn) {
- var range = moveFn(getRange());
- if (range && !evt.isDefaultPrevented()) {
- evt.preventDefault();
- setRange(range);
- }
- }
- function getContentEditableRoot(node) {
- var root = editor.getBody();
- while (node && node != root) {
- if (isContentEditableTrue(node) || isContentEditableFalse(node)) {
- return node;
- }
- node = node.parentNode;
- }
- return null;
- }
- function isXYWithinRange(clientX, clientY, range) {
- if (range.collapsed) {
- return false;
- }
- return Arr.reduce(range.getClientRects(), function(state, rect) {
- return state || ClientRect.containsXY(rect, clientX, clientY);
- }, false);
- }
- // Some browsers (Chrome) lets you place the caret after a cE=false
- // Make sure we render the caret container in this case
- editor.on('mouseup', function() {
- var range = getRange();
- if (range.collapsed) {
- setRange(renderCaretAtRange(range));
- }
- });
- editor.on('click', function(e) {
- var contentEditableRoot;
- // Prevent clicks on links in a cE=false element
- contentEditableRoot = getContentEditableRoot(e.target);
- if (contentEditableRoot) {
- if (isContentEditableFalse(contentEditableRoot)) {
- e.preventDefault();
- }
- }
- });
- function handleTouchSelect(editor) {
- var moved = false;
- editor.on('touchstart', function () {
- moved = false;
- });
- editor.on('touchmove', function () {
- moved = true;
- });
- editor.on('touchend', function (e) {
- var contentEditableRoot = getContentEditableRoot(e.target);
- if (isContentEditableFalse(contentEditableRoot)) {
- if (!moved) {
- e.preventDefault();
- setContentEditableSelection(selectNode(contentEditableRoot));
- }
- } else {
- clearContentEditableSelection();
- }
- });
- }
- var hasNormalCaretPosition = function (elm) {
- var caretWalker = new CaretWalker(elm);
- if (!elm.firstChild) {
- return false;
- }
- var startPos = CaretPosition.before(elm.firstChild);
- var newPos = caretWalker.next(startPos);
- return newPos && !isBeforeContentEditableFalse(newPos) && !isAfterContentEditableFalse(newPos);
- };
- var isInSameBlock = function (node1, node2) {
- var block1 = editor.dom.getParent(node1, editor.dom.isBlock);
- var block2 = editor.dom.getParent(node2, editor.dom.isBlock);
- return block1 === block2;
- };
- // Checks if the target node is in a block and if that block has a caret position better than the
- // suggested caretNode this is to prevent the caret from being sucked in towards a cE=false block if
- // they are adjacent on the vertical axis
- var hasBetterMouseTarget = function (targetNode, caretNode) {
- var targetBlock = editor.dom.getParent(targetNode, editor.dom.isBlock);
- var caretBlock = editor.dom.getParent(caretNode, editor.dom.isBlock);
- return targetBlock && !isInSameBlock(targetBlock, caretBlock) && hasNormalCaretPosition(targetBlock);
- };
- handleTouchSelect(editor);
- editor.on('mousedown', function(e) {
- var contentEditableRoot;
- contentEditableRoot = getContentEditableRoot(e.target);
- if (contentEditableRoot) {
- if (isContentEditableFalse(contentEditableRoot)) {
- e.preventDefault();
- setContentEditableSelection(selectNode(contentEditableRoot));
- } else {
- clearContentEditableSelection();
- if (!isXYWithinRange(e.clientX, e.clientY, editor.selection.getRng())) {
- editor.selection.placeCaretAt(e.clientX, e.clientY);
- }
- }
- } else {
- clearContentEditableSelection();
- fakeCaret.hide();
- var caretInfo = LineUtils.closestCaret(rootNode, e.clientX, e.clientY);
- if (caretInfo) {
- if (!hasBetterMouseTarget(e.target, caretInfo.node)) {
- e.preventDefault();
- editor.getBody().focus();
- setRange(showCaret(1, caretInfo.node, caretInfo.before));
- }
- }
- }
- });
- editor.on('keydown', function(e) {
- if (VK.modifierPressed(e)) {
- return;
- }
- switch (e.keyCode) {
- case VK.RIGHT:
- override(e, right);
- break;
- case VK.DOWN:
- override(e, down);
- break;
- case VK.LEFT:
- override(e, left);
- break;
- case VK.UP:
- override(e, up);
- break;
- case VK.DELETE:
- override(e, deleteForward);
- break;
- case VK.BACKSPACE:
- override(e, backspace);
- break;
- default:
- if (isContentEditableFalse(editor.selection.getNode())) {
- e.preventDefault();
- }
- break;
- }
- });
- function paddEmptyContentEditableArea() {
- var br, ceRoot = getContentEditableRoot(editor.selection.getNode());
- if (isContentEditableTrue(ceRoot) && isBlock(ceRoot) && editor.dom.isEmpty(ceRoot)) {
- br = editor.dom.create('br', {"data-mce-bogus": "1"});
- editor.$(ceRoot).empty().append(br);
- editor.selection.setRng(CaretPosition.before(br).toRange());
- }
- }
- function handleBlockContainer(e) {
- var blockCaretContainer = getBlockCaretContainer();
- if (!blockCaretContainer) {
- return;
- }
- if (e.type == 'compositionstart') {
- e.preventDefault();
- e.stopPropagation();
- showBlockCaretContainer(blockCaretContainer);
- return;
- }
- if (blockCaretContainer.innerHTML != ' ') {
- showBlockCaretContainer(blockCaretContainer);
- }
- }
- function handleEmptyBackspaceDelete(e) {
- var prevent;
- switch (e.keyCode) {
- case VK.DELETE:
- prevent = paddEmptyContentEditableArea();
- break;
- case VK.BACKSPACE:
- prevent = paddEmptyContentEditableArea();
- break;
- }
- if (prevent) {
- e.preventDefault();
- }
- }
- // Must be added to "top" since undoManager needs to be executed after
- editor.on('keyup compositionstart', function(e) {
- handleBlockContainer(e);
- handleEmptyBackspaceDelete(e);
- }, true);
- editor.on('cut', function() {
- var node = editor.selection.getNode();
- if (isContentEditableFalse(node)) {
- Delay.setEditorTimeout(editor, function() {
- setRange(renderRangeCaret(deleteContentEditableNode(node)));
- });
- }
- });
- editor.on('getSelectionRange', function(e) {
- var rng = e.range;
- if (selectedContentEditableNode) {
- if (!selectedContentEditableNode.parentNode) {
- selectedContentEditableNode = null;
- return;
- }
- rng = rng.cloneRange();
- rng.selectNode(selectedContentEditableNode);
- e.range = rng;
- }
- });
- editor.on('setSelectionRange', function(e) {
- var rng;
- rng = setContentEditableSelection(e.range);
- if (rng) {
- e.range = rng;
- }
- });
- editor.on('focus', function() {
- // Make sure we have a proper fake caret on focus
- Delay.setEditorTimeout(editor, function() {
- editor.selection.setRng(renderRangeCaret(editor.selection.getRng()));
- }, 0);
- });
- DragDropOverrides.init(editor);
- }
- function addCss() {
- var styles = editor.contentStyles, rootClass = '.mce-content-body';
- styles.push(fakeCaret.getCss());
- styles.push(
- rootClass + ' .mce-offscreen-selection {' +
- 'position: absolute;' +
- 'left: -9999999999px;' +
- 'width: 100px;' +
- 'height: 100px;' +
- '}' +
- rootClass + ' *[contentEditable=false] {' +
- 'cursor: default;' +
- '}' +
- rootClass + ' *[contentEditable=true] {' +
- 'cursor: text;' +
- '}'
- );
- }
- function isRangeInCaretContainer(rng) {
- return CaretContainer.isCaretContainer(rng.startContainer) || CaretContainer.isCaretContainer(rng.endContainer);
- }
- function setContentEditableSelection(range) {
- var node, $ = editor.$, dom = editor.dom, $realSelectionContainer, sel,
- startContainer, startOffset, endOffset, e, caretPosition, targetClone, origTargetClone;
- if (!range) {
- clearContentEditableSelection();
- return null;
- }
- if (range.collapsed) {
- clearContentEditableSelection();
- if (!isRangeInCaretContainer(range)) {
- caretPosition = getNormalizedRangeEndPoint(1, range);
- if (isContentEditableFalse(caretPosition.getNode())) {
- return showCaret(1, caretPosition.getNode(), !caretPosition.isAtEnd());
- }
- if (isContentEditableFalse(caretPosition.getNode(true))) {
- return showCaret(1, caretPosition.getNode(true), false);
- }
- }
- return null;
- }
- startContainer = range.startContainer;
- startOffset = range.startOffset;
- endOffset = range.endOffset;
- // Normalizes <span cE=false>[</span>] to [<span cE=false></span>]
- if (startContainer.nodeType == 3 && startOffset == 0 && isContentEditableFalse(startContainer.parentNode)) {
- startContainer = startContainer.parentNode;
- startOffset = dom.nodeIndex(startContainer);
- startContainer = startContainer.parentNode;
- }
- if (startContainer.nodeType != 1) {
- clearContentEditableSelection();
- return null;
- }
- if (endOffset == startOffset + 1) {
- node = startContainer.childNodes[startOffset];
- }
- if (!isContentEditableFalse(node)) {
- clearContentEditableSelection();
- return null;
- }
- targetClone = origTargetClone = node.cloneNode(true);
- e = editor.fire('ObjectSelected', {target: node, targetClone: targetClone});
- if (e.isDefaultPrevented()) {
- clearContentEditableSelection();
- return null;
- }
- targetClone = e.targetClone;
- $realSelectionContainer = $('#' + realSelectionId);
- if ($realSelectionContainer.length === 0) {
- $realSelectionContainer = $(
- '<div data-mce-bogus="all" class="mce-offscreen-selection"></div>'
- ).attr('id', realSelectionId);
- $realSelectionContainer.appendTo(editor.getBody());
- }
- range = editor.dom.createRng();
- // WHY is IE making things so hard! Copy on <i contentEditable="false">x</i> produces: <em>x</em>
- if (targetClone === origTargetClone && Env.ie) {
- $realSelectionContainer.empty().append(Zwsp.ZWSP).append(targetClone).append(Zwsp.ZWSP);
- range.setStart($realSelectionContainer[0].firstChild, 0);
- range.setEnd($realSelectionContainer[0].lastChild, 1);
- } else {
- $realSelectionContainer.empty().append('\u00a0').append(targetClone).append('\u00a0');
- range.setStart($realSelectionContainer[0].firstChild, 1);
- range.setEnd($realSelectionContainer[0].lastChild, 0);
- }
- $realSelectionContainer.css({
- top: dom.getPos(node, editor.getBody()).y
- });
- $realSelectionContainer[0].focus();
- sel = editor.selection.getSel();
- sel.removeAllRanges();
- sel.addRange(range);
- editor.$('*[data-mce-selected]').removeAttr('data-mce-selected');
- node.setAttribute('data-mce-selected', 1);
- selectedContentEditableNode = node;
- return range;
- }
- function clearContentEditableSelection() {
- if (selectedContentEditableNode) {
- selectedContentEditableNode.removeAttribute('data-mce-selected');
- editor.$('#' + realSelectionId).remove();
- selectedContentEditableNode = null;
- }
- }
- function destroy() {
- fakeCaret.destroy();
- selectedContentEditableNode = null;
- }
- function hideFakeCaret() {
- fakeCaret.hide();
- }
- if (Env.ceFalse) {
- registerEvents();
- addCss();
- }
- return {
- showBlockCaretContainer: showBlockCaretContainer,
- hideFakeCaret: hideFakeCaret,
- destroy: destroy
- };
- }
- return SelectionOverrides;
- });
- // Included from: js/tinymce/classes/util/Uuid.js
- /**
- * Uuid.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Generates unique ids.
- *
- * @class tinymce.util.Uuid
- * @private
- */
- define("tinymce/util/Uuid", [
- ], function() {
- var count = 0;
- var seed = function () {
- var rnd = function () {
- return Math.round(Math.random() * 0xFFFFFFFF).toString(36);
- };
- var now = new Date().getTime();
- return 's' + now.toString(36) + rnd() + rnd() + rnd();
- };
- var uuid = function (prefix) {
- return prefix + (count++) + seed();
- };
- return {
- uuid: uuid
- };
- });
- // Included from: js/tinymce/classes/Editor.js
- /**
- * Editor.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*jshint scripturl:true */
- /**
- * Include the base event class documentation.
- *
- * @include ../../../tools/docs/tinymce.Event.js
- */
- /**
- * This class contains the core logic for a TinyMCE editor.
- *
- * @class tinymce.Editor
- * @mixes tinymce.util.Observable
- * @example
- * // Add a class to all paragraphs in the editor.
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
- *
- * // Gets the current editors selection as text
- * tinymce.activeEditor.selection.getContent({format: 'text'});
- *
- * // Creates a new editor instance
- * var ed = new tinymce.Editor('textareaid', {
- * some_setting: 1
- * }, tinymce.EditorManager);
- *
- * // Select each item the user clicks on
- * ed.on('click', function(e) {
- * ed.selection.select(e.target);
- * });
- *
- * ed.render();
- */
- define("tinymce/Editor", [
- "tinymce/dom/DOMUtils",
- "tinymce/dom/DomQuery",
- "tinymce/AddOnManager",
- "tinymce/NodeChange",
- "tinymce/html/Node",
- "tinymce/dom/Serializer",
- "tinymce/html/Serializer",
- "tinymce/dom/Selection",
- "tinymce/Formatter",
- "tinymce/UndoManager",
- "tinymce/EnterKey",
- "tinymce/ForceBlocks",
- "tinymce/EditorCommands",
- "tinymce/util/URI",
- "tinymce/dom/ScriptLoader",
- "tinymce/dom/EventUtils",
- "tinymce/WindowManager",
- "tinymce/NotificationManager",
- "tinymce/html/Schema",
- "tinymce/html/DomParser",
- "tinymce/util/Quirks",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/util/Delay",
- "tinymce/EditorObservable",
- "tinymce/Mode",
- "tinymce/Shortcuts",
- "tinymce/EditorUpload",
- "tinymce/SelectionOverrides",
- "tinymce/util/Uuid"
- ], function(
- DOMUtils, DomQuery, AddOnManager, NodeChange, Node, DomSerializer, Serializer,
- Selection, Formatter, UndoManager, EnterKey, ForceBlocks, EditorCommands,
- URI, ScriptLoader, EventUtils, WindowManager, NotificationManager,
- Schema, DomParser, Quirks, Env, Tools, Delay, EditorObservable, Mode, Shortcuts, EditorUpload,
- SelectionOverrides, Uuid
- ) {
- // Shorten these names
- var DOM = DOMUtils.DOM, ThemeManager = AddOnManager.ThemeManager, PluginManager = AddOnManager.PluginManager;
- var extend = Tools.extend, each = Tools.each, explode = Tools.explode;
- var inArray = Tools.inArray, trim = Tools.trim, resolve = Tools.resolve;
- var Event = EventUtils.Event;
- var isGecko = Env.gecko, ie = Env.ie;
- /**
- * Include documentation for all the events.
- *
- * @include ../../../tools/docs/tinymce.Editor.js
- */
- /**
- * Constructs a editor instance by id.
- *
- * @constructor
- * @method Editor
- * @param {String} id Unique id for the editor.
- * @param {Object} settings Settings for the editor.
- * @param {tinymce.EditorManager} editorManager EditorManager instance.
- */
- function Editor(id, settings, editorManager) {
- var self = this, documentBaseUrl, baseUri, defaultSettings;
- documentBaseUrl = self.documentBaseUrl = editorManager.documentBaseURL;
- baseUri = editorManager.baseURI;
- defaultSettings = editorManager.defaultSettings;
- /**
- * Name/value collection with editor settings.
- *
- * @property settings
- * @type Object
- * @example
- * // Get the value of the theme setting
- * tinymce.activeEditor.windowManager.alert("You are using the " + tinymce.activeEditor.settings.theme + " theme");
- */
- settings = extend({
- id: id,
- theme: 'modern',
- delta_width: 0,
- delta_height: 0,
- popup_css: '',
- plugins: '',
- document_base_url: documentBaseUrl,
- add_form_submit_trigger: true,
- submit_patch: true,
- add_unload_trigger: true,
- convert_urls: true,
- relative_urls: true,
- remove_script_host: true,
- object_resizing: true,
- doctype: '<!DOCTYPE html>',
- visual: true,
- font_size_style_values: 'xx-small,x-small,small,medium,large,x-large,xx-large',
- // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
- font_size_legacy_values: 'xx-small,small,medium,large,x-large,xx-large,300%',
- forced_root_block: 'p',
- hidden_input: true,
- padd_empty_editor: true,
- render_ui: true,
- indentation: '30px',
- inline_styles: true,
- convert_fonts_to_spans: true,
- indent: 'simple',
- indent_before: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
- indent_after: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' +
- 'tfoot,tbody,tr,section,article,hgroup,aside,figure,figcaption,option,optgroup,datalist',
- validate: true,
- entity_encoding: 'named',
- url_converter: self.convertURL,
- url_converter_scope: self,
- ie7_compat: true
- }, defaultSettings, settings);
- // Merge external_plugins
- if (defaultSettings && defaultSettings.external_plugins && settings.external_plugins) {
- settings.external_plugins = extend({}, defaultSettings.external_plugins, settings.external_plugins);
- }
- self.settings = settings;
- AddOnManager.language = settings.language || 'en';
- AddOnManager.languageLoad = settings.language_load;
- AddOnManager.baseURL = editorManager.baseURL;
- /**
- * Editor instance id, normally the same as the div/textarea that was replaced.
- *
- * @property id
- * @type String
- */
- self.id = settings.id = id;
- /**
- * State to force the editor to return false on a isDirty call.
- *
- * @property isNotDirty
- * @type Boolean
- * @deprecated Use editor.setDirty instead.
- */
- self.setDirty(false);
- /**
- * Name/Value object containing plugin instances.
- *
- * @property plugins
- * @type Object
- * @example
- * // Execute a method inside a plugin directly
- * tinymce.activeEditor.plugins.someplugin.someMethod();
- */
- self.plugins = {};
- /**
- * URI object to document configured for the TinyMCE instance.
- *
- * @property documentBaseURI
- * @type tinymce.util.URI
- * @example
- * // Get relative URL from the location of document_base_url
- * tinymce.activeEditor.documentBaseURI.toRelative('/somedir/somefile.htm');
- *
- * // Get absolute URL from the location of document_base_url
- * tinymce.activeEditor.documentBaseURI.toAbsolute('somefile.htm');
- */
- self.documentBaseURI = new URI(settings.document_base_url || documentBaseUrl, {
- base_uri: baseUri
- });
- /**
- * URI object to current document that holds the TinyMCE editor instance.
- *
- * @property baseURI
- * @type tinymce.util.URI
- * @example
- * // Get relative URL from the location of the API
- * tinymce.activeEditor.baseURI.toRelative('/somedir/somefile.htm');
- *
- * // Get absolute URL from the location of the API
- * tinymce.activeEditor.baseURI.toAbsolute('somefile.htm');
- */
- self.baseURI = baseUri;
- /**
- * Array with CSS files to load into the iframe.
- *
- * @property contentCSS
- * @type Array
- */
- self.contentCSS = [];
- /**
- * Array of CSS styles to add to head of document when the editor loads.
- *
- * @property contentStyles
- * @type Array
- */
- self.contentStyles = [];
- // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
- self.shortcuts = new Shortcuts(self);
- self.loadedCSS = {};
- self.editorCommands = new EditorCommands(self);
- if (settings.target) {
- self.targetElm = settings.target;
- }
- self.suffix = editorManager.suffix;
- self.editorManager = editorManager;
- self.inline = settings.inline;
- self.settings.content_editable = self.inline;
- if (settings.cache_suffix) {
- Env.cacheSuffix = settings.cache_suffix.replace(/^[\?\&]+/, '');
- }
- if (settings.override_viewport === false) {
- Env.overrideViewPort = false;
- }
- // Call setup
- editorManager.fire('SetupEditor', self);
- self.execCallback('setup', self);
- /**
- * Dom query instance with default scope to the editor document and default element is the body of the editor.
- *
- * @property $
- * @type tinymce.dom.DomQuery
- * @example
- * tinymce.activeEditor.$('p').css('color', 'red');
- * tinymce.activeEditor.$().append('<p>new</p>');
- */
- self.$ = DomQuery.overrideDefaults(function() {
- return {
- context: self.inline ? self.getBody() : self.getDoc(),
- element: self.getBody()
- };
- });
- }
- Editor.prototype = {
- /**
- * Renders the editor/adds it to the page.
- *
- * @method render
- */
- render: function() {
- var self = this, settings = self.settings, id = self.id, suffix = self.suffix;
- function readyHandler() {
- DOM.unbind(window, 'ready', readyHandler);
- self.render();
- }
- // Page is not loaded yet, wait for it
- if (!Event.domLoaded) {
- DOM.bind(window, 'ready', readyHandler);
- return;
- }
- // Element not found, then skip initialization
- if (!self.getElement()) {
- return;
- }
- // No editable support old iOS versions etc
- if (!Env.contentEditable) {
- return;
- }
- // Hide target element early to prevent content flashing
- if (!settings.inline) {
- self.orgVisibility = self.getElement().style.visibility;
- self.getElement().style.visibility = 'hidden';
- } else {
- self.inline = true;
- }
- var form = self.getElement().form || DOM.getParent(id, 'form');
- if (form) {
- self.formElement = form;
- // Add hidden input for non input elements inside form elements
- if (settings.hidden_input && !/TEXTAREA|INPUT/i.test(self.getElement().nodeName)) {
- DOM.insertAfter(DOM.create('input', {type: 'hidden', name: id}), id);
- self.hasHiddenInput = true;
- }
- // Pass submit/reset from form to editor instance
- self.formEventDelegate = function(e) {
- self.fire(e.type, e);
- };
- DOM.bind(form, 'submit reset', self.formEventDelegate);
- // Reset contents in editor when the form is reset
- self.on('reset', function() {
- self.setContent(self.startContent, {format: 'raw'});
- });
- // Check page uses id="submit" or name="submit" for it's submit button
- if (settings.submit_patch && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
- form._mceOldSubmit = form.submit;
- form.submit = function() {
- self.editorManager.triggerSave();
- self.setDirty(false);
- return form._mceOldSubmit(form);
- };
- }
- }
- /**
- * Window manager reference, use this to open new windows and dialogs.
- *
- * @property windowManager
- * @type tinymce.WindowManager
- * @example
- * // Shows an alert message
- * tinymce.activeEditor.windowManager.alert('Hello world!');
- *
- * // Opens a new dialog with the file.htm file and the size 320x240
- * // It also adds a custom parameter this can be retrieved by using tinyMCEPopup.getWindowArg inside the dialog.
- * tinymce.activeEditor.windowManager.open({
- * url: 'file.htm',
- * width: 320,
- * height: 240
- * }, {
- * custom_param: 1
- * });
- */
- self.windowManager = new WindowManager(self);
- /**
- * Notification manager reference, use this to open new windows and dialogs.
- *
- * @property notificationManager
- * @type tinymce.NotificationManager
- * @example
- * // Shows a notification info message.
- * tinymce.activeEditor.notificationManager.open({text: 'Hello world!', type: 'info'});
- */
- self.notificationManager = new NotificationManager(self);
- if (settings.encoding == 'xml') {
- self.on('GetContent', function(e) {
- if (e.save) {
- e.content = DOM.encode(e.content);
- }
- });
- }
- if (settings.add_form_submit_trigger) {
- self.on('submit', function() {
- if (self.initialized) {
- self.save();
- }
- });
- }
- if (settings.add_unload_trigger) {
- self._beforeUnload = function() {
- if (self.initialized && !self.destroyed && !self.isHidden()) {
- self.save({format: 'raw', no_events: true, set_dirty: false});
- }
- };
- self.editorManager.on('BeforeUnload', self._beforeUnload);
- }
- // Load scripts
- function loadScripts() {
- var scriptLoader = ScriptLoader.ScriptLoader;
- if (settings.language && settings.language != 'en' && !settings.language_url) {
- settings.language_url = self.editorManager.baseURL + '/langs/' + settings.language + '.js';
- }
- if (settings.language_url) {
- scriptLoader.add(settings.language_url);
- }
- if (settings.theme && typeof settings.theme != "function" &&
- settings.theme.charAt(0) != '-' && !ThemeManager.urls[settings.theme]) {
- var themeUrl = settings.theme_url;
- if (themeUrl) {
- themeUrl = self.documentBaseURI.toAbsolute(themeUrl);
- } else {
- themeUrl = 'themes/' + settings.theme + '/theme' + suffix + '.js';
- }
- ThemeManager.load(settings.theme, themeUrl);
- }
- if (Tools.isArray(settings.plugins)) {
- settings.plugins = settings.plugins.join(' ');
- }
- each(settings.external_plugins, function(url, name) {
- PluginManager.load(name, url);
- settings.plugins += ' ' + name;
- });
- each(settings.plugins.split(/[ ,]/), function(plugin) {
- plugin = trim(plugin);
- if (plugin && !PluginManager.urls[plugin]) {
- if (plugin.charAt(0) == '-') {
- plugin = plugin.substr(1, plugin.length);
- var dependencies = PluginManager.dependencies(plugin);
- each(dependencies, function(dep) {
- var defaultSettings = {
- prefix: 'plugins/',
- resource: dep,
- suffix: '/plugin' + suffix + '.js'
- };
- dep = PluginManager.createUrl(defaultSettings, dep);
- PluginManager.load(dep.resource, dep);
- });
- } else {
- PluginManager.load(plugin, {
- prefix: 'plugins/',
- resource: plugin,
- suffix: '/plugin' + suffix + '.js'
- });
- }
- }
- });
- scriptLoader.loadQueue(function() {
- if (!self.removed) {
- self.init();
- }
- });
- }
- self.editorManager.add(self);
- loadScripts();
- },
- /**
- * Initializes the editor this will be called automatically when
- * all plugins/themes and language packs are loaded by the rendered method.
- * This method will setup the iframe and create the theme and plugin instances.
- *
- * @method init
- */
- init: function() {
- var self = this, settings = self.settings, elm = self.getElement();
- var w, h, minHeight, n, o, Theme, url, bodyId, bodyClass, re, i, initializedPlugins = [];
- this.editorManager.i18n.setCode(settings.language);
- self.rtl = settings.rtl_ui || this.editorManager.i18n.rtl;
- settings.aria_label = settings.aria_label || DOM.getAttrib(elm, 'aria-label', self.getLang('aria.rich_text_area'));
- /**
- * Reference to the theme instance that was used to generate the UI.
- *
- * @property theme
- * @type tinymce.Theme
- * @example
- * // Executes a method on the theme directly
- * tinymce.activeEditor.theme.someMethod();
- */
- if (settings.theme) {
- if (typeof settings.theme != "function") {
- settings.theme = settings.theme.replace(/-/, '');
- Theme = ThemeManager.get(settings.theme);
- self.theme = new Theme(self, ThemeManager.urls[settings.theme]);
- if (self.theme.init) {
- self.theme.init(self, ThemeManager.urls[settings.theme] || self.documentBaseUrl.replace(/\/$/, ''), self.$);
- }
- } else {
- self.theme = settings.theme;
- }
- }
- function initPlugin(plugin) {
- var Plugin = PluginManager.get(plugin), pluginUrl, pluginInstance;
- pluginUrl = PluginManager.urls[plugin] || self.documentBaseUrl.replace(/\/$/, '');
- plugin = trim(plugin);
- if (Plugin && inArray(initializedPlugins, plugin) === -1) {
- each(PluginManager.dependencies(plugin), function(dep) {
- initPlugin(dep);
- });
- if (self.plugins[plugin]) {
- return;
- }
- pluginInstance = new Plugin(self, pluginUrl, self.$);
- self.plugins[plugin] = pluginInstance;
- if (pluginInstance.init) {
- pluginInstance.init(self, pluginUrl);
- initializedPlugins.push(plugin);
- }
- }
- }
- // Create all plugins
- each(settings.plugins.replace(/\-/g, '').split(/[ ,]/), initPlugin);
- // Measure box
- if (settings.render_ui && self.theme) {
- self.orgDisplay = elm.style.display;
- if (typeof settings.theme != "function") {
- w = settings.width || elm.style.width || elm.offsetWidth;
- h = settings.height || elm.style.height || elm.offsetHeight;
- minHeight = settings.min_height || 100;
- re = /^[0-9\.]+(|px)$/i;
- if (re.test('' + w)) {
- w = Math.max(parseInt(w, 10), 100);
- }
- if (re.test('' + h)) {
- h = Math.max(parseInt(h, 10), minHeight);
- }
- // Render UI
- o = self.theme.renderUI({
- targetNode: elm,
- width: w,
- height: h,
- deltaWidth: settings.delta_width,
- deltaHeight: settings.delta_height
- });
- // Resize editor
- if (!settings.content_editable) {
- h = (o.iframeHeight || h) + (typeof h == 'number' ? (o.deltaHeight || 0) : '');
- if (h < minHeight) {
- h = minHeight;
- }
- }
- } else {
- o = settings.theme(self, elm);
- // Convert element type to id:s
- if (o.editorContainer.nodeType) {
- o.editorContainer = o.editorContainer.id = o.editorContainer.id || self.id + "_parent";
- }
- // Convert element type to id:s
- if (o.iframeContainer.nodeType) {
- o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || self.id + "_iframecontainer";
- }
- // Use specified iframe height or the targets offsetHeight
- h = o.iframeHeight || elm.offsetHeight;
- }
- self.editorContainer = o.editorContainer;
- }
- // Load specified content CSS last
- if (settings.content_css) {
- each(explode(settings.content_css), function(u) {
- self.contentCSS.push(self.documentBaseURI.toAbsolute(u));
- });
- }
- // Load specified content CSS last
- if (settings.content_style) {
- self.contentStyles.push(settings.content_style);
- }
- // Content editable mode ends here
- if (settings.content_editable) {
- elm = n = o = null; // Fix IE leak
- return self.initContentBody();
- }
- self.iframeHTML = settings.doctype + '<html><head>';
- // We only need to override paths if we have to
- // IE has a bug where it remove site absolute urls to relative ones if this is specified
- if (settings.document_base_url != self.documentBaseUrl) {
- self.iframeHTML += '<base href="' + self.documentBaseURI.getURI() + '" />';
- }
- // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
- if (!Env.caretAfter && settings.ie7_compat) {
- self.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
- }
- self.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
- // Load the CSS by injecting them into the HTML this will reduce "flicker"
- // However we can't do that on Chrome since # will scroll to the editor for some odd reason see #2427
- if (!/#$/.test(document.location.href)) {
- for (i = 0; i < self.contentCSS.length; i++) {
- var cssUrl = self.contentCSS[i];
- self.iframeHTML += (
- '<link type="text/css" ' +
- 'rel="stylesheet" ' +
- 'href="' + Tools._addCacheSuffix(cssUrl) + '" />'
- );
- self.loadedCSS[cssUrl] = true;
- }
- }
- bodyId = settings.body_id || 'tinymce';
- if (bodyId.indexOf('=') != -1) {
- bodyId = self.getParam('body_id', '', 'hash');
- bodyId = bodyId[self.id] || bodyId;
- }
- bodyClass = settings.body_class || '';
- if (bodyClass.indexOf('=') != -1) {
- bodyClass = self.getParam('body_class', '', 'hash');
- bodyClass = bodyClass[self.id] || '';
- }
- if (settings.content_security_policy) {
- self.iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + settings.content_security_policy + '" />';
- }
- self.iframeHTML += '</head><body id="' + bodyId +
- '" class="mce-content-body ' + bodyClass +
- '" data-id="' + self.id + '"><br></body></html>';
- /*eslint no-script-url:0 */
- var domainRelaxUrl = 'javascript:(function(){' +
- 'document.open();document.domain="' + document.domain + '";' +
- 'var ed = window.parent.tinymce.get("' + self.id + '");document.write(ed.iframeHTML);' +
- 'document.close();ed.initContentBody(true);})()';
- // Domain relaxing is required since the user has messed around with document.domain
- if (document.domain != location.hostname) {
- // Edge seems to be able to handle domain relaxing
- if (Env.ie && Env.ie < 12) {
- url = domainRelaxUrl;
- }
- }
- // Create iframe
- // TODO: ACC add the appropriate description on this.
- var ifr = DOM.create('iframe', {
- id: self.id + "_ifr",
- //src: url || 'javascript:""', // Workaround for HTTPS warning in IE6/7
- frameBorder: '0',
- allowTransparency: "true",
- title: self.editorManager.translate(
- "Rich Text Area. Press ALT-F9 for menu. " +
- "Press ALT-F10 for toolbar. Press ALT-0 for help"
- ),
- style: {
- width: '100%',
- height: h,
- display: 'block' // Important for Gecko to render the iframe correctly
- }
- });
- ifr.onload = function() {
- ifr.onload = null;
- self.fire("load");
- };
- DOM.setAttrib(ifr, "src", url || 'javascript:""');
- self.contentAreaContainer = o.iframeContainer;
- self.iframeElement = ifr;
- n = DOM.add(o.iframeContainer, ifr);
- // Try accessing the document this will fail on IE when document.domain is set to the same as location.hostname
- // Then we have to force domain relaxing using the domainRelaxUrl approach very ugly!!
- if (ie) {
- try {
- self.getDoc();
- } catch (e) {
- n.src = url = domainRelaxUrl;
- }
- }
- if (o.editorContainer) {
- DOM.get(o.editorContainer).style.display = self.orgDisplay;
- self.hidden = DOM.isHidden(o.editorContainer);
- }
- self.getElement().style.display = 'none';
- DOM.setAttrib(self.id, 'aria-hidden', true);
- if (!url) {
- self.initContentBody();
- }
- elm = n = o = null; // Cleanup
- },
- /**
- * This method get called by the init method once the iframe is loaded.
- * It will fill the iframe with contents, sets up DOM and selection objects for the iframe.
- *
- * @method initContentBody
- * @private
- */
- initContentBody: function(skipWrite) {
- var self = this, settings = self.settings, targetElm = self.getElement(), doc = self.getDoc(), body, contentCssText;
- // Restore visibility on target element
- if (!settings.inline) {
- self.getElement().style.visibility = self.orgVisibility;
- }
- // Setup iframe body
- if (!skipWrite && !settings.content_editable) {
- doc.open();
- doc.write(self.iframeHTML);
- doc.close();
- }
- if (settings.content_editable) {
- self.on('remove', function() {
- var bodyEl = this.getBody();
- DOM.removeClass(bodyEl, 'mce-content-body');
- DOM.removeClass(bodyEl, 'mce-edit-focus');
- DOM.setAttrib(bodyEl, 'contentEditable', null);
- });
- DOM.addClass(targetElm, 'mce-content-body');
- self.contentDocument = doc = settings.content_document || document;
- self.contentWindow = settings.content_window || window;
- self.bodyElement = targetElm;
- // Prevent leak in IE
- settings.content_document = settings.content_window = null;
- // TODO: Fix this
- settings.root_name = targetElm.nodeName.toLowerCase();
- }
- // It will not steal focus while setting contentEditable
- body = self.getBody();
- body.disabled = true;
- self.readonly = settings.readonly;
- if (!self.readonly) {
- if (self.inline && DOM.getStyle(body, 'position', true) == 'static') {
- body.style.position = 'relative';
- }
- body.contentEditable = self.getParam('content_editable_state', true);
- }
- body.disabled = false;
- self.editorUpload = new EditorUpload(self);
- /**
- * Schema instance, enables you to validate elements and its children.
- *
- * @property schema
- * @type tinymce.html.Schema
- */
- self.schema = new Schema(settings);
- /**
- * DOM instance for the editor.
- *
- * @property dom
- * @type tinymce.dom.DOMUtils
- * @example
- * // Adds a class to all paragraphs within the editor
- * tinymce.activeEditor.dom.addClass(tinymce.activeEditor.dom.select('p'), 'someclass');
- */
- self.dom = new DOMUtils(doc, {
- keep_values: true,
- url_converter: self.convertURL,
- url_converter_scope: self,
- hex_colors: settings.force_hex_style_colors,
- class_filter: settings.class_filter,
- update_styles: true,
- root_element: self.inline ? self.getBody() : null,
- collect: settings.content_editable,
- schema: self.schema,
- onSetAttrib: function(e) {
- self.fire('SetAttrib', e);
- }
- });
- /**
- * HTML parser will be used when contents is inserted into the editor.
- *
- * @property parser
- * @type tinymce.html.DomParser
- */
- self.parser = new DomParser(settings, self.schema);
- // Convert src and href into data-mce-src, data-mce-href and data-mce-style
- self.parser.addAttributeFilter('src,href,style,tabindex', function(nodes, name) {
- var i = nodes.length, node, dom = self.dom, value, internalName;
- while (i--) {
- node = nodes[i];
- value = node.attr(name);
- internalName = 'data-mce-' + name;
- // Add internal attribute if we need to we don't on a refresh of the document
- if (!node.attributes.map[internalName]) {
- // Don't duplicate these since they won't get modified by any browser
- if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) {
- continue;
- }
- if (name === "style") {
- value = dom.serializeStyle(dom.parseStyle(value), node.name);
- if (!value.length) {
- value = null;
- }
- node.attr(internalName, value);
- node.attr(name, value);
- } else if (name === "tabindex") {
- node.attr(internalName, value);
- node.attr(name, null);
- } else {
- node.attr(internalName, self.convertURL(value, name, node.name));
- }
- }
- }
- });
- // Keep scripts from executing
- self.parser.addNodeFilter('script', function(nodes) {
- var i = nodes.length, node, type;
- while (i--) {
- node = nodes[i];
- type = node.attr('type') || 'no/type';
- if (type.indexOf('mce-') !== 0) {
- node.attr('type', 'mce-' + type);
- }
- }
- });
- self.parser.addNodeFilter('#cdata', function(nodes) {
- var i = nodes.length, node;
- while (i--) {
- node = nodes[i];
- node.type = 8;
- node.name = '#comment';
- node.value = '[CDATA[' + node.value + ']]';
- }
- });
- self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes) {
- var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
- while (i--) {
- node = nodes[i];
- if (node.isEmpty(nonEmptyElements)) {
- node.append(new Node('br', 1)).shortEnded = true;
- }
- }
- });
- /**
- * DOM serializer for the editor. Will be used when contents is extracted from the editor.
- *
- * @property serializer
- * @type tinymce.dom.Serializer
- * @example
- * // Serializes the first paragraph in the editor into a string
- * tinymce.activeEditor.serializer.serialize(tinymce.activeEditor.dom.select('p')[0]);
- */
- self.serializer = new DomSerializer(settings, self);
- /**
- * Selection instance for the editor.
- *
- * @property selection
- * @type tinymce.dom.Selection
- * @example
- * // Sets some contents to the current selection in the editor
- * tinymce.activeEditor.selection.setContent('Some contents');
- *
- * // Gets the current selection
- * alert(tinymce.activeEditor.selection.getContent());
- *
- * // Selects the first paragraph found
- * tinymce.activeEditor.selection.select(tinymce.activeEditor.dom.select('p')[0]);
- */
- self.selection = new Selection(self.dom, self.getWin(), self.serializer, self);
- /**
- * Formatter instance.
- *
- * @property formatter
- * @type tinymce.Formatter
- */
- self.formatter = new Formatter(self);
- /**
- * Undo manager instance, responsible for handling undo levels.
- *
- * @property undoManager
- * @type tinymce.UndoManager
- * @example
- * // Undoes the last modification to the editor
- * tinymce.activeEditor.undoManager.undo();
- */
- self.undoManager = new UndoManager(self);
- self.forceBlocks = new ForceBlocks(self);
- self.enterKey = new EnterKey(self);
- self._nodeChangeDispatcher = new NodeChange(self);
- self._selectionOverrides = new SelectionOverrides(self);
- self.fire('PreInit');
- if (!settings.browser_spellcheck && !settings.gecko_spellcheck) {
- doc.body.spellcheck = false; // Gecko
- DOM.setAttrib(body, "spellcheck", "false");
- }
- self.quirks = new Quirks(self);
- self.fire('PostRender');
- if (settings.directionality) {
- body.dir = settings.directionality;
- }
- if (settings.nowrap) {
- body.style.whiteSpace = "nowrap";
- }
- if (settings.protect) {
- self.on('BeforeSetContent', function(e) {
- each(settings.protect, function(pattern) {
- e.content = e.content.replace(pattern, function(str) {
- return '<!--mce:protected ' + escape(str) + '-->';
- });
- });
- });
- }
- self.on('SetContent', function() {
- self.addVisual(self.getBody());
- });
- // Remove empty contents
- if (settings.padd_empty_editor) {
- self.on('PostProcess', function(e) {
- e.content = e.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
- });
- }
- self.load({initial: true, format: 'html'});
- self.startContent = self.getContent({format: 'raw'});
- /**
- * Is set to true after the editor instance has been initialized
- *
- * @property initialized
- * @type Boolean
- * @example
- * function isEditorInitialized(editor) {
- * return editor && editor.initialized;
- * }
- */
- self.initialized = true;
- self.bindPendingEventDelegates();
- self.fire('init');
- self.focus(true);
- self.nodeChanged({initial: true});
- self.execCallback('init_instance_callback', self);
- self.on('compositionstart compositionend', function(e) {
- self.composing = e.type === 'compositionstart';
- });
- // Add editor specific CSS styles
- if (self.contentStyles.length > 0) {
- contentCssText = '';
- each(self.contentStyles, function(style) {
- contentCssText += style + "\r\n";
- });
- self.dom.addStyle(contentCssText);
- }
- // Load specified content CSS last
- each(self.contentCSS, function(cssUrl) {
- if (!self.loadedCSS[cssUrl]) {
- self.dom.loadCSS(cssUrl);
- self.loadedCSS[cssUrl] = true;
- }
- });
- // Handle auto focus
- if (settings.auto_focus) {
- Delay.setEditorTimeout(self, function() {
- var editor;
- if (settings.auto_focus === true) {
- editor = self;
- } else {
- editor = self.editorManager.get(settings.auto_focus);
- }
- if (!editor.destroyed) {
- editor.focus();
- }
- }, 100);
- }
- // Clean up references for IE
- targetElm = doc = body = null;
- },
- /**
- * Focuses/activates the editor. This will set this editor as the activeEditor in the tinymce collection
- * it will also place DOM focus inside the editor.
- *
- * @method focus
- * @param {Boolean} skipFocus Skip DOM focus. Just set is as the active editor.
- */
- focus: function(skipFocus) {
- var self = this, selection = self.selection, contentEditable = self.settings.content_editable, rng;
- var controlElm, doc = self.getDoc(), body = self.getBody(), contentEditableHost;
- function getContentEditableHost(node) {
- return self.dom.getParent(node, function(node) {
- return self.dom.getContentEditable(node) === "true";
- });
- }
- if (!skipFocus) {
- // Get selected control element
- rng = selection.getRng();
- if (rng.item) {
- controlElm = rng.item(0);
- }
- self.quirks.refreshContentEditable();
- // Move focus to contentEditable=true child if needed
- contentEditableHost = getContentEditableHost(selection.getNode());
- if (self.$.contains(body, contentEditableHost)) {
- contentEditableHost.focus();
- selection.normalize();
- self.editorManager.setActive(self);
- return;
- }
- // Focus the window iframe
- if (!contentEditable) {
- // WebKit needs this call to fire focusin event properly see #5948
- // But Opera pre Blink engine will produce an empty selection so skip Opera
- if (!Env.opera) {
- self.getBody().focus();
- }
- self.getWin().focus();
- }
- // Focus the body as well since it's contentEditable
- if (isGecko || contentEditable) {
- // Check for setActive since it doesn't scroll to the element
- if (body.setActive) {
- // IE 11 sometimes throws "Invalid function" then fallback to focus
- try {
- body.setActive();
- } catch (ex) {
- body.focus();
- }
- } else {
- body.focus();
- }
- if (contentEditable) {
- selection.normalize();
- }
- }
- // Restore selected control element
- // This is needed when for example an image is selected within a
- // layer a call to focus will then remove the control selection
- if (controlElm && controlElm.ownerDocument == doc) {
- rng = doc.body.createControlRange();
- rng.addElement(controlElm);
- rng.select();
- }
- }
- self.editorManager.setActive(self);
- },
- /**
- * Executes a legacy callback. This method is useful to call old 2.x option callbacks.
- * There new event model is a better way to add callback so this method might be removed in the future.
- *
- * @method execCallback
- * @param {String} name Name of the callback to execute.
- * @return {Object} Return value passed from callback function.
- */
- execCallback: function(name) {
- var self = this, callback = self.settings[name], scope;
- if (!callback) {
- return;
- }
- // Look through lookup
- if (self.callbackLookup && (scope = self.callbackLookup[name])) {
- callback = scope.func;
- scope = scope.scope;
- }
- if (typeof callback === 'string') {
- scope = callback.replace(/\.\w+$/, '');
- scope = scope ? resolve(scope) : 0;
- callback = resolve(callback);
- self.callbackLookup = self.callbackLookup || {};
- self.callbackLookup[name] = {func: callback, scope: scope};
- }
- return callback.apply(scope || self, Array.prototype.slice.call(arguments, 1));
- },
- /**
- * Translates the specified string by replacing variables with language pack items it will also check if there is
- * a key matching the input.
- *
- * @method translate
- * @param {String} text String to translate by the language pack data.
- * @return {String} Translated string.
- */
- translate: function(text) {
- var lang = this.settings.language || 'en', i18n = this.editorManager.i18n;
- if (!text) {
- return '';
- }
- text = i18n.data[lang + '.' + text] || text.replace(/\{\#([^\}]+)\}/g, function(a, b) {
- return i18n.data[lang + '.' + b] || '{#' + b + '}';
- });
- return this.editorManager.translate(text);
- },
- /**
- * Returns a language pack item by name/key.
- *
- * @method getLang
- * @param {String} name Name/key to get from the language pack.
- * @param {String} defaultVal Optional default value to retrieve.
- */
- getLang: function(name, defaultVal) {
- return (
- this.editorManager.i18n.data[(this.settings.language || 'en') + '.' + name] ||
- (defaultVal !== undefined ? defaultVal : '{#' + name + '}')
- );
- },
- /**
- * Returns a configuration parameter by name.
- *
- * @method getParam
- * @param {String} name Configruation parameter to retrieve.
- * @param {String} defaultVal Optional default value to return.
- * @param {String} type Optional type parameter.
- * @return {String} Configuration parameter value or default value.
- * @example
- * // Returns a specific config value from the currently active editor
- * var someval = tinymce.activeEditor.getParam('myvalue');
- *
- * // Returns a specific config value from a specific editor instance by id
- * var someval2 = tinymce.get('my_editor').getParam('myvalue');
- */
- getParam: function(name, defaultVal, type) {
- var value = name in this.settings ? this.settings[name] : defaultVal, output;
- if (type === 'hash') {
- output = {};
- if (typeof value === 'string') {
- each(value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(','), function(value) {
- value = value.split('=');
- if (value.length > 1) {
- output[trim(value[0])] = trim(value[1]);
- } else {
- output[trim(value[0])] = trim(value);
- }
- });
- } else {
- output = value;
- }
- return output;
- }
- return value;
- },
- /**
- * Dispatches out a onNodeChange event to all observers. This method should be called when you
- * need to update the UI states or element path etc.
- *
- * @method nodeChanged
- * @param {Object} args Optional args to pass to NodeChange event handlers.
- */
- nodeChanged: function(args) {
- this._nodeChangeDispatcher.nodeChanged(args);
- },
- /**
- * Adds a button that later gets created by the theme in the editors toolbars.
- *
- * @method addButton
- * @param {String} name Button name to add.
- * @param {Object} settings Settings object with title, cmd etc.
- * @example
- * // Adds a custom button to the editor that inserts contents when clicked
- * tinymce.init({
- * ...
- *
- * toolbar: 'example'
- *
- * setup: function(ed) {
- * ed.addButton('example', {
- * title: 'My title',
- * image: '../js/tinymce/plugins/example/img/example.gif',
- * onclick: function() {
- * ed.insertContent('Hello world!!');
- * }
- * });
- * }
- * });
- */
- addButton: function(name, settings) {
- var self = this;
- if (settings.cmd) {
- settings.onclick = function() {
- self.execCommand(settings.cmd);
- };
- }
- if (!settings.text && !settings.icon) {
- settings.icon = name;
- }
- self.buttons = self.buttons || {};
- settings.tooltip = settings.tooltip || settings.title;
- self.buttons[name] = settings;
- },
- /**
- * Adds a menu item to be used in the menus of the theme. There might be multiple instances
- * of this menu item for example it might be used in the main menus of the theme but also in
- * the context menu so make sure that it's self contained and supports multiple instances.
- *
- * @method addMenuItem
- * @param {String} name Menu item name to add.
- * @param {Object} settings Settings object with title, cmd etc.
- * @example
- * // Adds a custom menu item to the editor that inserts contents when clicked
- * // The context option allows you to add the menu item to an existing default menu
- * tinymce.init({
- * ...
- *
- * setup: function(ed) {
- * ed.addMenuItem('example', {
- * text: 'My menu item',
- * context: 'tools',
- * onclick: function() {
- * ed.insertContent('Hello world!!');
- * }
- * });
- * }
- * });
- */
- addMenuItem: function(name, settings) {
- var self = this;
- if (settings.cmd) {
- settings.onclick = function() {
- self.execCommand(settings.cmd);
- };
- }
- self.menuItems = self.menuItems || {};
- self.menuItems[name] = settings;
- },
- /**
- * Adds a contextual toolbar to be rendered when the selector matches.
- *
- * @method addContextToolbar
- * @param {function/string} predicate Predicate that needs to return true if provided strings get converted into CSS predicates.
- * @param {String/Array} items String or array with items to add to the context toolbar.
- */
- addContextToolbar: function(predicate, items) {
- var self = this, selector;
- self.contextToolbars = self.contextToolbars || [];
- // Convert selector to predicate
- if (typeof predicate == "string") {
- selector = predicate;
- predicate = function(elm) {
- return self.dom.is(elm, selector);
- };
- }
- self.contextToolbars.push({
- id: Uuid.uuid('mcet'),
- predicate: predicate,
- items: items
- });
- },
- /**
- * Adds a custom command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with execCommand.
- *
- * @method addCommand
- * @param {String} name Command name to add/override.
- * @param {addCommandCallback} callback Function to execute when the command occurs.
- * @param {Object} scope Optional scope to execute the function in.
- * @example
- * // Adds a custom command that later can be executed using execCommand
- * tinymce.init({
- * ...
- *
- * setup: function(ed) {
- * // Register example command
- * ed.addCommand('mycommand', function(ui, v) {
- * ed.windowManager.alert('Hello world!! Selection: ' + ed.selection.getContent({format: 'text'}));
- * });
- * }
- * });
- */
- addCommand: function(name, callback, scope) {
- /**
- * Callback function that gets called when a command is executed.
- *
- * @callback addCommandCallback
- * @param {Boolean} ui Display UI state true/false.
- * @param {Object} value Optional value for command.
- * @return {Boolean} True/false state if the command was handled or not.
- */
- this.editorCommands.addCommand(name, callback, scope);
- },
- /**
- * Adds a custom query state command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with queryCommandState function.
- *
- * @method addQueryStateHandler
- * @param {String} name Command name to add/override.
- * @param {addQueryStateHandlerCallback} callback Function to execute when the command state retrieval occurs.
- * @param {Object} scope Optional scope to execute the function in.
- */
- addQueryStateHandler: function(name, callback, scope) {
- /**
- * Callback function that gets called when a queryCommandState is executed.
- *
- * @callback addQueryStateHandlerCallback
- * @return {Boolean} True/false state if the command is enabled or not like is it bold.
- */
- this.editorCommands.addQueryStateHandler(name, callback, scope);
- },
- /**
- * Adds a custom query value command to the editor, you can also override existing commands with this method.
- * The command that you add can be executed with queryCommandValue function.
- *
- * @method addQueryValueHandler
- * @param {String} name Command name to add/override.
- * @param {addQueryValueHandlerCallback} callback Function to execute when the command value retrieval occurs.
- * @param {Object} scope Optional scope to execute the function in.
- */
- addQueryValueHandler: function(name, callback, scope) {
- /**
- * Callback function that gets called when a queryCommandValue is executed.
- *
- * @callback addQueryValueHandlerCallback
- * @return {Object} Value of the command or undefined.
- */
- this.editorCommands.addQueryValueHandler(name, callback, scope);
- },
- /**
- * Adds a keyboard shortcut for some command or function.
- *
- * @method addShortcut
- * @param {String} pattern Shortcut pattern. Like for example: ctrl+alt+o.
- * @param {String} desc Text description for the command.
- * @param {String/Function} cmdFunc Command name string or function to execute when the key is pressed.
- * @param {Object} sc Optional scope to execute the function in.
- * @return {Boolean} true/false state if the shortcut was added or not.
- */
- addShortcut: function(pattern, desc, cmdFunc, scope) {
- this.shortcuts.add(pattern, desc, cmdFunc, scope);
- },
- /**
- * Executes a command on the current instance. These commands can be TinyMCE internal commands prefixed with "mce" or
- * they can be build in browser commands such as "Bold". A compleate list of browser commands is available on MSDN or Mozilla.org.
- * This function will dispatch the execCommand function on each plugin, theme or the execcommand_callback option if none of these
- * return true it will handle the command as a internal browser command.
- *
- * @method execCommand
- * @param {String} cmd Command name to execute, for example mceLink or Bold.
- * @param {Boolean} ui True/false state if a UI (dialog) should be presented or not.
- * @param {mixed} value Optional command value, this can be anything.
- * @param {Object} args Optional arguments object.
- */
- execCommand: function(cmd, ui, value, args) {
- return this.editorCommands.execCommand(cmd, ui, value, args);
- },
- /**
- * Returns a command specific state, for example if bold is enabled or not.
- *
- * @method queryCommandState
- * @param {string} cmd Command to query state from.
- * @return {Boolean} Command specific state, for example if bold is enabled or not.
- */
- queryCommandState: function(cmd) {
- return this.editorCommands.queryCommandState(cmd);
- },
- /**
- * Returns a command specific value, for example the current font size.
- *
- * @method queryCommandValue
- * @param {string} cmd Command to query value from.
- * @return {Object} Command specific value, for example the current font size.
- */
- queryCommandValue: function(cmd) {
- return this.editorCommands.queryCommandValue(cmd);
- },
- /**
- * Returns true/false if the command is supported or not.
- *
- * @method queryCommandSupported
- * @param {String} cmd Command that we check support for.
- * @return {Boolean} true/false if the command is supported or not.
- */
- queryCommandSupported: function(cmd) {
- return this.editorCommands.queryCommandSupported(cmd);
- },
- /**
- * Shows the editor and hides any textarea/div that the editor is supposed to replace.
- *
- * @method show
- */
- show: function() {
- var self = this;
- if (self.hidden) {
- self.hidden = false;
- if (self.inline) {
- self.getBody().contentEditable = true;
- } else {
- DOM.show(self.getContainer());
- DOM.hide(self.id);
- }
- self.load();
- self.fire('show');
- }
- },
- /**
- * Hides the editor and shows any textarea/div that the editor is supposed to replace.
- *
- * @method hide
- */
- hide: function() {
- var self = this, doc = self.getDoc();
- if (!self.hidden) {
- // Fixed bug where IE has a blinking cursor left from the editor
- if (ie && doc && !self.inline) {
- doc.execCommand('SelectAll');
- }
- // We must save before we hide so Safari doesn't crash
- self.save();
- if (self.inline) {
- self.getBody().contentEditable = false;
- // Make sure the editor gets blurred
- if (self == self.editorManager.focusedEditor) {
- self.editorManager.focusedEditor = null;
- }
- } else {
- DOM.hide(self.getContainer());
- DOM.setStyle(self.id, 'display', self.orgDisplay);
- }
- self.hidden = true;
- self.fire('hide');
- }
- },
- /**
- * Returns true/false if the editor is hidden or not.
- *
- * @method isHidden
- * @return {Boolean} True/false if the editor is hidden or not.
- */
- isHidden: function() {
- return !!this.hidden;
- },
- /**
- * Sets the progress state, this will display a throbber/progess for the editor.
- * This is ideal for asynchronous operations like an AJAX save call.
- *
- * @method setProgressState
- * @param {Boolean} state Boolean state if the progress should be shown or hidden.
- * @param {Number} time Optional time to wait before the progress gets shown.
- * @return {Boolean} Same as the input state.
- * @example
- * // Show progress for the active editor
- * tinymce.activeEditor.setProgressState(true);
- *
- * // Hide progress for the active editor
- * tinymce.activeEditor.setProgressState(false);
- *
- * // Show progress after 3 seconds
- * tinymce.activeEditor.setProgressState(true, 3000);
- */
- setProgressState: function(state, time) {
- this.fire('ProgressState', {state: state, time: time});
- },
- /**
- * Loads contents from the textarea or div element that got converted into an editor instance.
- * This method will move the contents from that textarea or div into the editor by using setContent
- * so all events etc that method has will get dispatched as well.
- *
- * @method load
- * @param {Object} args Optional content object, this gets passed around through the whole load process.
- * @return {String} HTML string that got set into the editor.
- */
- load: function(args) {
- var self = this, elm = self.getElement(), html;
- if (elm) {
- args = args || {};
- args.load = true;
- html = self.setContent(elm.value !== undefined ? elm.value : elm.innerHTML, args);
- args.element = elm;
- if (!args.no_events) {
- self.fire('LoadContent', args);
- }
- args.element = elm = null;
- return html;
- }
- },
- /**
- * Saves the contents from a editor out to the textarea or div element that got converted into an editor instance.
- * This method will move the HTML contents from the editor into that textarea or div by getContent
- * so all events etc that method has will get dispatched as well.
- *
- * @method save
- * @param {Object} args Optional content object, this gets passed around through the whole save process.
- * @return {String} HTML string that got set into the textarea/div.
- */
- save: function(args) {
- var self = this, elm = self.getElement(), html, form;
- if (!elm || !self.initialized) {
- return;
- }
- args = args || {};
- args.save = true;
- args.element = elm;
- html = args.content = self.getContent(args);
- if (!args.no_events) {
- self.fire('SaveContent', args);
- }
- // Always run this internal event
- if (args.format == 'raw') {
- self.fire('RawSaveContent', args);
- }
- html = args.content;
- if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
- // Update DIV element when not in inline mode
- if (!self.inline) {
- elm.innerHTML = html;
- }
- // Update hidden form element
- if ((form = DOM.getParent(self.id, 'form'))) {
- each(form.elements, function(elm) {
- if (elm.name == self.id) {
- elm.value = html;
- return false;
- }
- });
- }
- } else {
- elm.value = html;
- }
- args.element = elm = null;
- if (args.set_dirty !== false) {
- self.setDirty(false);
- }
- return html;
- },
- /**
- * Sets the specified content to the editor instance, this will cleanup the content before it gets set using
- * the different cleanup rules options.
- *
- * @method setContent
- * @param {String} content Content to set to editor, normally HTML contents but can be other formats as well.
- * @param {Object} args Optional content object, this gets passed around through the whole set process.
- * @return {String} HTML string that got set into the editor.
- * @example
- * // Sets the HTML contents of the activeEditor editor
- * tinymce.activeEditor.setContent('<span>some</span> html');
- *
- * // Sets the raw contents of the activeEditor editor
- * tinymce.activeEditor.setContent('<span>some</span> html', {format: 'raw'});
- *
- * // Sets the content of a specific editor (my_editor in this example)
- * tinymce.get('my_editor').setContent(data);
- *
- * // Sets the bbcode contents of the activeEditor editor if the bbcode plugin was added
- * tinymce.activeEditor.setContent('[b]some[/b] html', {format: 'bbcode'});
- */
- setContent: function(content, args) {
- var self = this, body = self.getBody(), forcedRootBlockName, padd;
- // Setup args object
- args = args || {};
- args.format = args.format || 'html';
- args.set = true;
- args.content = content;
- // Do preprocessing
- if (!args.no_events) {
- self.fire('BeforeSetContent', args);
- }
- content = args.content;
- // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
- // It will also be impossible to place the caret in the editor unless there is a BR element present
- if (content.length === 0 || /^\s+$/.test(content)) {
- padd = ie && ie < 11 ? '' : '<br data-mce-bogus="1">';
- // Todo: There is a lot more root elements that need special padding
- // so separate this and add all of them at some point.
- if (body.nodeName == 'TABLE') {
- content = '<tr><td>' + padd + '</td></tr>';
- } else if (/^(UL|OL)$/.test(body.nodeName)) {
- content = '<li>' + padd + '</li>';
- }
- forcedRootBlockName = self.settings.forced_root_block;
- // Check if forcedRootBlock is configured and that the block is a valid child of the body
- if (forcedRootBlockName && self.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
- // Padd with bogus BR elements on modern browsers and IE 7 and 8 since they don't render empty P tags properly
- content = padd;
- content = self.dom.createHTML(forcedRootBlockName, self.settings.forced_root_block_attrs, content);
- } else if (!ie && !content) {
- // We need to add a BR when forced_root_block is disabled on non IE browsers to place the caret
- content = '<br data-mce-bogus="1">';
- }
- self.dom.setHTML(body, content);
- self.fire('SetContent', args);
- } else {
- // Parse and serialize the html
- if (args.format !== 'raw') {
- content = new Serializer({
- validate: self.validate
- }, self.schema).serialize(
- self.parser.parse(content, {isRootContent: true})
- );
- }
- // Set the new cleaned contents to the editor
- args.content = trim(content);
- self.dom.setHTML(body, args.content);
- // Do post processing
- if (!args.no_events) {
- self.fire('SetContent', args);
- }
- // Don't normalize selection if the focused element isn't the body in
- // content editable mode since it will steal focus otherwise
- /*if (!self.settings.content_editable || document.activeElement === self.getBody()) {
- self.selection.normalize();
- }*/
- }
- return args.content;
- },
- /**
- * Gets the content from the editor instance, this will cleanup the content before it gets returned using
- * the different cleanup rules options.
- *
- * @method getContent
- * @param {Object} args Optional content object, this gets passed around through the whole get process.
- * @return {String} Cleaned content string, normally HTML contents.
- * @example
- * // Get the HTML contents of the currently active editor
- * console.debug(tinymce.activeEditor.getContent());
- *
- * // Get the raw contents of the currently active editor
- * tinymce.activeEditor.getContent({format: 'raw'});
- *
- * // Get content of a specific editor:
- * tinymce.get('content id').getContent()
- */
- getContent: function(args) {
- var self = this, content, body = self.getBody();
- // Setup args object
- args = args || {};
- args.format = args.format || 'html';
- args.get = true;
- args.getInner = true;
- // Do preprocessing
- if (!args.no_events) {
- self.fire('BeforeGetContent', args);
- }
- // Get raw contents or by default the cleaned contents
- if (args.format == 'raw') {
- content = self.serializer.getTrimmedContent();
- } else if (args.format == 'text') {
- content = body.innerText || body.textContent;
- } else {
- content = self.serializer.serialize(body, args);
- }
- // Trim whitespace in beginning/end of HTML
- if (args.format != 'text') {
- args.content = trim(content);
- } else {
- args.content = content;
- }
- // Do post processing
- if (!args.no_events) {
- self.fire('GetContent', args);
- }
- return args.content;
- },
- /**
- * Inserts content at caret position.
- *
- * @method insertContent
- * @param {String} content Content to insert.
- * @param {Object} args Optional args to pass to insert call.
- */
- insertContent: function(content, args) {
- if (args) {
- content = extend({content: content}, args);
- }
- this.execCommand('mceInsertContent', false, content);
- },
- /**
- * Returns true/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
- *
- * The dirty state is automatically set to true if you do modifications to the content in other
- * words when new undo levels is created or if you undo/redo to update the contents of the editor. It will also be set
- * to false if you call editor.save().
- *
- * @method isDirty
- * @return {Boolean} True/false if the editor is dirty or not. It will get dirty if the user has made modifications to the contents.
- * @example
- * if (tinymce.activeEditor.isDirty())
- * alert("You must save your contents.");
- */
- isDirty: function() {
- return !this.isNotDirty;
- },
- /**
- * Explicitly sets the dirty state. This will fire the dirty event if the editor dirty state is changed from false to true
- * by invoking this method.
- *
- * @method setDirty
- * @param {Boolean} state True/false if the editor is considered dirty.
- * @example
- * function ajaxSave() {
- * var editor = tinymce.get('elm1');
- *
- * // Save contents using some XHR call
- * alert(editor.getContent());
- *
- * editor.setDirty(false); // Force not dirty state
- * }
- */
- setDirty: function(state) {
- var oldState = !this.isNotDirty;
- this.isNotDirty = !state;
- if (state && state != oldState) {
- this.fire('dirty');
- }
- },
- /**
- * Sets the editor mode. Mode can be for example "design", "code" or "readonly".
- *
- * @method setMode
- * @param {String} mode Mode to set the editor in.
- */
- setMode: function(mode) {
- Mode.setMode(this, mode);
- },
- /**
- * Returns the editors container element. The container element wrappes in
- * all the elements added to the page for the editor. Such as UI, iframe etc.
- *
- * @method getContainer
- * @return {Element} HTML DOM element for the editor container.
- */
- getContainer: function() {
- var self = this;
- if (!self.container) {
- self.container = DOM.get(self.editorContainer || self.id + '_parent');
- }
- return self.container;
- },
- /**
- * Returns the editors content area container element. The this element is the one who
- * holds the iframe or the editable element.
- *
- * @method getContentAreaContainer
- * @return {Element} HTML DOM element for the editor area container.
- */
- getContentAreaContainer: function() {
- return this.contentAreaContainer;
- },
- /**
- * Returns the target element/textarea that got replaced with a TinyMCE editor instance.
- *
- * @method getElement
- * @return {Element} HTML DOM element for the replaced element.
- */
- getElement: function() {
- if (!this.targetElm) {
- this.targetElm = DOM.get(this.id);
- }
- return this.targetElm;
- },
- /**
- * Returns the iframes window object.
- *
- * @method getWin
- * @return {Window} Iframe DOM window object.
- */
- getWin: function() {
- var self = this, elm;
- if (!self.contentWindow) {
- elm = self.iframeElement;
- if (elm) {
- self.contentWindow = elm.contentWindow;
- }
- }
- return self.contentWindow;
- },
- /**
- * Returns the iframes document object.
- *
- * @method getDoc
- * @return {Document} Iframe DOM document object.
- */
- getDoc: function() {
- var self = this, win;
- if (!self.contentDocument) {
- win = self.getWin();
- if (win) {
- self.contentDocument = win.document;
- }
- }
- return self.contentDocument;
- },
- /**
- * Returns the root element of the editable area.
- * For a non-inline iframe-based editor, returns the iframe's body element.
- *
- * @method getBody
- * @return {Element} The root element of the editable area.
- */
- getBody: function() {
- return this.bodyElement || this.getDoc().body;
- },
- /**
- * URL converter function this gets executed each time a user adds an img, a or
- * any other element that has a URL in it. This will be called both by the DOM and HTML
- * manipulation functions.
- *
- * @method convertURL
- * @param {string} url URL to convert.
- * @param {string} name Attribute name src, href etc.
- * @param {string/HTMLElement} elm Tag name or HTML DOM element depending on HTML or DOM insert.
- * @return {string} Converted URL string.
- */
- convertURL: function(url, name, elm) {
- var self = this, settings = self.settings;
- // Use callback instead
- if (settings.urlconverter_callback) {
- return self.execCallback('urlconverter_callback', url, elm, true, name);
- }
- // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
- if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0 || url.length === 0) {
- return url;
- }
- // Convert to relative
- if (settings.relative_urls) {
- return self.documentBaseURI.toRelative(url);
- }
- // Convert to absolute
- url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
- return url;
- },
- /**
- * Adds visual aid for tables, anchors etc so they can be more easily edited inside the editor.
- *
- * @method addVisual
- * @param {Element} elm Optional root element to loop though to find tables etc that needs the visual aid.
- */
- addVisual: function(elm) {
- var self = this, settings = self.settings, dom = self.dom, cls;
- elm = elm || self.getBody();
- if (self.hasVisual === undefined) {
- self.hasVisual = settings.visual;
- }
- each(dom.select('table,a', elm), function(elm) {
- var value;
- switch (elm.nodeName) {
- case 'TABLE':
- cls = settings.visual_table_class || 'mce-item-table';
- value = dom.getAttrib(elm, 'border');
- if ((!value || value == '0') && self.hasVisual) {
- dom.addClass(elm, cls);
- } else {
- dom.removeClass(elm, cls);
- }
- return;
- case 'A':
- if (!dom.getAttrib(elm, 'href', false)) {
- value = dom.getAttrib(elm, 'name') || elm.id;
- cls = settings.visual_anchor_class || 'mce-item-anchor';
- if (value && self.hasVisual) {
- dom.addClass(elm, cls);
- } else {
- dom.removeClass(elm, cls);
- }
- }
- return;
- }
- });
- self.fire('VisualAid', {element: elm, hasVisual: self.hasVisual});
- },
- /**
- * Removes the editor from the dom and tinymce collection.
- *
- * @method remove
- */
- remove: function() {
- var self = this;
- if (!self.removed) {
- self.save();
- self.removed = 1;
- self.unbindAllNativeEvents();
- // Remove any hidden input
- if (self.hasHiddenInput) {
- DOM.remove(self.getElement().nextSibling);
- }
- if (!self.inline) {
- // IE 9 has a bug where the selection stops working if you place the
- // caret inside the editor then remove the iframe
- if (ie && ie < 10) {
- self.getDoc().execCommand('SelectAll', false, null);
- }
- DOM.setStyle(self.id, 'display', self.orgDisplay);
- self.getBody().onload = null; // Prevent #6816
- }
- self.fire('remove');
- self.editorManager.remove(self);
- DOM.remove(self.getContainer());
- self._selectionOverrides.destroy();
- self.editorUpload.destroy();
- self.destroy();
- }
- },
- /**
- * Destroys the editor instance by removing all events, element references or other resources
- * that could leak memory. This method will be called automatically when the page is unloaded
- * but you can also call it directly if you know what you are doing.
- *
- * @method destroy
- * @param {Boolean} automatic Optional state if the destroy is an automatic destroy or user called one.
- */
- destroy: function(automatic) {
- var self = this, form;
- // One time is enough
- if (self.destroyed) {
- return;
- }
- // If user manually calls destroy and not remove
- // Users seems to have logic that calls destroy instead of remove
- if (!automatic && !self.removed) {
- self.remove();
- return;
- }
- if (!automatic) {
- self.editorManager.off('beforeunload', self._beforeUnload);
- // Manual destroy
- if (self.theme && self.theme.destroy) {
- self.theme.destroy();
- }
- // Destroy controls, selection and dom
- self.selection.destroy();
- self.dom.destroy();
- }
- form = self.formElement;
- if (form) {
- if (form._mceOldSubmit) {
- form.submit = form._mceOldSubmit;
- form._mceOldSubmit = null;
- }
- DOM.unbind(form, 'submit reset', self.formEventDelegate);
- }
- self.contentAreaContainer = self.formElement = self.container = self.editorContainer = null;
- self.bodyElement = self.contentDocument = self.contentWindow = null;
- self.iframeElement = self.targetElm = null;
- if (self.selection) {
- self.selection = self.selection.win = self.selection.dom = self.selection.dom.doc = null;
- }
- self.destroyed = 1;
- },
- /**
- * Uploads all data uri/blob uri images in the editor contents to server.
- *
- * @method uploadImages
- * @param {function} callback Optional callback with images and status for each image.
- * @return {tinymce.util.Promise} Promise instance.
- */
- uploadImages: function(callback) {
- return this.editorUpload.uploadImages(callback);
- },
- // Internal functions
- _scanForImages: function() {
- return this.editorUpload.scanForImages();
- }
- };
- extend(Editor.prototype, EditorObservable);
- return Editor;
- });
- // Included from: js/tinymce/classes/util/I18n.js
- /**
- * I18n.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * I18n class that handles translation of TinyMCE UI.
- * Uses po style with csharp style parameters.
- *
- * @class tinymce.util.I18n
- */
- define("tinymce/util/I18n", [], function() {
- "use strict";
- var data = {}, code = "en";
- return {
- /**
- * Sets the current language code.
- *
- * @method setCode
- * @param {String} newCode Current language code.
- */
- setCode: function(newCode) {
- if (newCode) {
- code = newCode;
- this.rtl = this.data[newCode] ? this.data[newCode]._dir === 'rtl' : false;
- }
- },
- /**
- * Returns the current language code.
- *
- * @method getCode
- * @return {String} Current language code.
- */
- getCode: function() {
- return code;
- },
- /**
- * Property gets set to true if a RTL language pack was loaded.
- *
- * @property rtl
- * @type Boolean
- */
- rtl: false,
- /**
- * Adds translations for a specific language code.
- *
- * @method add
- * @param {String} code Language code like sv_SE.
- * @param {Array} items Name/value array with English en_US to sv_SE.
- */
- add: function(code, items) {
- var langData = data[code];
- if (!langData) {
- data[code] = langData = {};
- }
- for (var name in items) {
- langData[name] = items[name];
- }
- this.setCode(code);
- },
- /**
- * Translates the specified text.
- *
- * It has a few formats:
- * I18n.translate("Text");
- * I18n.translate(["Text {0}/{1}", 0, 1]);
- * I18n.translate({raw: "Raw string"});
- *
- * @method translate
- * @param {String/Object/Array} text Text to translate.
- * @return {String} String that got translated.
- */
- translate: function(text) {
- var langData;
- langData = data[code];
- if (!langData) {
- langData = {};
- }
- if (typeof text == "undefined") {
- return text;
- }
- if (typeof text != "string" && text.raw) {
- return text.raw;
- }
- if (text.push) {
- var values = text.slice(1);
- text = (langData[text[0]] || text[0]).replace(/\{([0-9]+)\}/g, function(match1, match2) {
- return values[match2];
- });
- }
- return (langData[text] || text).replace(/{context:\w+}$/, '');
- },
- data: data
- };
- });
- // Included from: js/tinymce/classes/FocusManager.js
- /**
- * FocusManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class manages the focus/blur state of the editor. This class is needed since some
- * browsers fire false focus/blur states when the selection is moved to a UI dialog or similar.
- *
- * This class will fire two events focus and blur on the editor instances that got affected.
- * It will also handle the restore of selection when the focus is lost and returned.
- *
- * @class tinymce.FocusManager
- */
- define("tinymce/FocusManager", [
- "tinymce/dom/DOMUtils",
- "tinymce/util/Delay",
- "tinymce/Env"
- ], function(DOMUtils, Delay, Env) {
- var selectionChangeHandler, documentFocusInHandler, documentMouseUpHandler, DOM = DOMUtils.DOM;
- /**
- * Constructs a new focus manager instance.
- *
- * @constructor FocusManager
- * @param {tinymce.EditorManager} editorManager Editor manager instance to handle focus for.
- */
- function FocusManager(editorManager) {
- function getActiveElement() {
- try {
- return document.activeElement;
- } catch (ex) {
- // IE sometimes fails to get the activeElement when resizing table
- // TODO: Investigate this
- return document.body;
- }
- }
- // We can't store a real range on IE 11 since it gets mutated so we need to use a bookmark object
- // TODO: Move this to a separate range utils class since it's it's logic is present in Selection as well.
- function createBookmark(dom, rng) {
- if (rng && rng.startContainer) {
- // Verify that the range is within the root of the editor
- if (!dom.isChildOf(rng.startContainer, dom.getRoot()) || !dom.isChildOf(rng.endContainer, dom.getRoot())) {
- return;
- }
- return {
- startContainer: rng.startContainer,
- startOffset: rng.startOffset,
- endContainer: rng.endContainer,
- endOffset: rng.endOffset
- };
- }
- return rng;
- }
- function bookmarkToRng(editor, bookmark) {
- var rng;
- if (bookmark.startContainer) {
- rng = editor.getDoc().createRange();
- rng.setStart(bookmark.startContainer, bookmark.startOffset);
- rng.setEnd(bookmark.endContainer, bookmark.endOffset);
- } else {
- rng = bookmark;
- }
- return rng;
- }
- function isUIElement(elm) {
- return !!DOM.getParent(elm, FocusManager.isEditorUIElement);
- }
- function registerEvents(e) {
- var editor = e.editor;
- editor.on('init', function() {
- // Gecko/WebKit has ghost selections in iframes and IE only has one selection per browser tab
- if (editor.inline || Env.ie) {
- // Use the onbeforedeactivate event when available since it works better see #7023
- if ("onbeforedeactivate" in document && Env.ie < 9) {
- editor.dom.bind(editor.getBody(), 'beforedeactivate', function(e) {
- if (e.target != editor.getBody()) {
- return;
- }
- try {
- editor.lastRng = editor.selection.getRng();
- } catch (ex) {
- // IE throws "Unexcpected call to method or property access" some times so lets ignore it
- }
- });
- } else {
- // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
- editor.on('nodechange mouseup keyup', function(e) {
- var node = getActiveElement();
- // Only act on manual nodechanges
- if (e.type == 'nodechange' && e.selectionChange) {
- return;
- }
- // IE 11 reports active element as iframe not body of iframe
- if (node && node.id == editor.id + '_ifr') {
- node = editor.getBody();
- }
- if (editor.dom.isChildOf(node, editor.getBody())) {
- editor.lastRng = editor.selection.getRng();
- }
- });
- }
- // Handles the issue with WebKit not retaining selection within inline document
- // If the user releases the mouse out side the body since a mouse up event wont occur on the body
- if (Env.webkit && !selectionChangeHandler) {
- selectionChangeHandler = function() {
- var activeEditor = editorManager.activeEditor;
- if (activeEditor && activeEditor.selection) {
- var rng = activeEditor.selection.getRng();
- // Store when it's non collapsed
- if (rng && !rng.collapsed) {
- editor.lastRng = rng;
- }
- }
- };
- DOM.bind(document, 'selectionchange', selectionChangeHandler);
- }
- }
- });
- editor.on('setcontent', function() {
- editor.lastRng = null;
- });
- // Remove last selection bookmark on mousedown see #6305
- editor.on('mousedown', function() {
- editor.selection.lastFocusBookmark = null;
- });
- editor.on('focusin', function() {
- var focusedEditor = editorManager.focusedEditor, lastRng;
- if (editor.selection.lastFocusBookmark) {
- lastRng = bookmarkToRng(editor, editor.selection.lastFocusBookmark);
- editor.selection.lastFocusBookmark = null;
- editor.selection.setRng(lastRng);
- }
- if (focusedEditor != editor) {
- if (focusedEditor) {
- focusedEditor.fire('blur', {focusedEditor: editor});
- }
- editorManager.setActive(editor);
- editorManager.focusedEditor = editor;
- editor.fire('focus', {blurredEditor: focusedEditor});
- editor.focus(true);
- }
- editor.lastRng = null;
- });
- editor.on('focusout', function() {
- Delay.setEditorTimeout(editor, function() {
- var focusedEditor = editorManager.focusedEditor;
- // Still the same editor the blur was outside any editor UI
- if (!isUIElement(getActiveElement()) && focusedEditor == editor) {
- editor.fire('blur', {focusedEditor: null});
- editorManager.focusedEditor = null;
- // Make sure selection is valid could be invalid if the editor is blured and removed before the timeout occurs
- if (editor.selection) {
- editor.selection.lastFocusBookmark = null;
- }
- }
- });
- });
- // Check if focus is moved to an element outside the active editor by checking if the target node
- // isn't within the body of the activeEditor nor a UI element such as a dialog child control
- if (!documentFocusInHandler) {
- documentFocusInHandler = function(e) {
- var activeEditor = editorManager.activeEditor, target;
- target = e.target;
- if (activeEditor && target.ownerDocument == document) {
- // Check to make sure we have a valid selection don't update the bookmark if it's
- // a focusin to the body of the editor see #7025
- if (activeEditor.selection && target != activeEditor.getBody()) {
- activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.dom, activeEditor.lastRng);
- }
- // Fire a blur event if the element isn't a UI element
- if (target != document.body && !isUIElement(target) && editorManager.focusedEditor == activeEditor) {
- activeEditor.fire('blur', {focusedEditor: null});
- editorManager.focusedEditor = null;
- }
- }
- };
- DOM.bind(document, 'focusin', documentFocusInHandler);
- }
- // Handle edge case when user starts the selection inside the editor and releases
- // the mouse outside the editor producing a new selection. This weird workaround is needed since
- // Gecko doesn't have the "selectionchange" event we need to do this. Fixes: #6843
- if (editor.inline && !documentMouseUpHandler) {
- documentMouseUpHandler = function(e) {
- var activeEditor = editorManager.activeEditor, dom = activeEditor.dom;
- if (activeEditor.inline && dom && !dom.isChildOf(e.target, activeEditor.getBody())) {
- var rng = activeEditor.selection.getRng();
- if (!rng.collapsed) {
- activeEditor.lastRng = rng;
- }
- }
- };
- DOM.bind(document, 'mouseup', documentMouseUpHandler);
- }
- }
- function unregisterDocumentEvents(e) {
- if (editorManager.focusedEditor == e.editor) {
- editorManager.focusedEditor = null;
- }
- if (!editorManager.activeEditor) {
- DOM.unbind(document, 'selectionchange', selectionChangeHandler);
- DOM.unbind(document, 'focusin', documentFocusInHandler);
- DOM.unbind(document, 'mouseup', documentMouseUpHandler);
- selectionChangeHandler = documentFocusInHandler = documentMouseUpHandler = null;
- }
- }
- editorManager.on('AddEditor', registerEvents);
- editorManager.on('RemoveEditor', unregisterDocumentEvents);
- }
- /**
- * Returns true if the specified element is part of the UI for example an button or text input.
- *
- * @method isEditorUIElement
- * @param {Element} elm Element to check if it's part of the UI or not.
- * @return {Boolean} True/false state if the element is part of the UI or not.
- */
- FocusManager.isEditorUIElement = function(elm) {
- // Needs to be converted to string since svg can have focus: #6776
- return elm.className.toString().indexOf('mce-') !== -1;
- };
- return FocusManager;
- });
- // Included from: js/tinymce/classes/EditorManager.js
- /**
- * EditorManager.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class used as a factory for manager for tinymce.Editor instances.
- *
- * @example
- * tinymce.EditorManager.init({});
- *
- * @class tinymce.EditorManager
- * @mixes tinymce.util.Observable
- * @static
- */
- define("tinymce/EditorManager", [
- "tinymce/Editor",
- "tinymce/dom/DomQuery",
- "tinymce/dom/DOMUtils",
- "tinymce/util/URI",
- "tinymce/Env",
- "tinymce/util/Tools",
- "tinymce/util/Promise",
- "tinymce/util/Observable",
- "tinymce/util/I18n",
- "tinymce/FocusManager"
- ], function(Editor, $, DOMUtils, URI, Env, Tools, Promise, Observable, I18n, FocusManager) {
- var DOM = DOMUtils.DOM;
- var explode = Tools.explode, each = Tools.each, extend = Tools.extend;
- var instanceCounter = 0, beforeUnloadDelegate, EditorManager, boundGlobalEvents = false;
- function globalEventDelegate(e) {
- each(EditorManager.editors, function(editor) {
- if (e.type === 'scroll') {
- editor.fire('ScrollWindow', e);
- } else {
- editor.fire('ResizeWindow', e);
- }
- });
- }
- function toggleGlobalEvents(editors, state) {
- if (state !== boundGlobalEvents) {
- if (state) {
- $(window).on('resize scroll', globalEventDelegate);
- } else {
- $(window).off('resize scroll', globalEventDelegate);
- }
- boundGlobalEvents = state;
- }
- }
- function removeEditorFromList(editor) {
- var editors = EditorManager.editors, removedFromList;
- delete editors[editor.id];
- for (var i = 0; i < editors.length; i++) {
- if (editors[i] == editor) {
- editors.splice(i, 1);
- removedFromList = true;
- break;
- }
- }
- // Select another editor since the active one was removed
- if (EditorManager.activeEditor == editor) {
- EditorManager.activeEditor = editors[0];
- }
- // Clear focusedEditor if necessary, so that we don't try to blur the destroyed editor
- if (EditorManager.focusedEditor == editor) {
- EditorManager.focusedEditor = null;
- }
- return removedFromList;
- }
- function purgeDestroyedEditor(editor) {
- // User has manually destroyed the editor lets clean up the mess
- if (editor && editor.initialized && !(editor.getContainer() || editor.getBody()).parentNode) {
- removeEditorFromList(editor);
- editor.unbindAllNativeEvents();
- editor.destroy(true);
- editor.removed = true;
- editor = null;
- }
- return editor;
- }
- EditorManager = {
- /**
- * Dom query instance.
- *
- * @property $
- * @type tinymce.dom.DomQuery
- */
- $: $,
- /**
- * Major version of TinyMCE build.
- *
- * @property majorVersion
- * @type String
- */
- majorVersion: '4',
- /**
- * Minor version of TinyMCE build.
- *
- * @property minorVersion
- * @type String
- */
- minorVersion: '4.1',
- /**
- * Release date of TinyMCE build.
- *
- * @property releaseDate
- * @type String
- */
- releaseDate: '2016-07-26',
- /**
- * Collection of editor instances.
- *
- * @property editors
- * @type Object
- * @example
- * for (edId in tinymce.editors)
- * tinymce.editors[edId].save();
- */
- editors: [],
- /**
- * Collection of language pack data.
- *
- * @property i18n
- * @type Object
- */
- i18n: I18n,
- /**
- * Currently active editor instance.
- *
- * @property activeEditor
- * @type tinymce.Editor
- * @example
- * tinyMCE.activeEditor.selection.getContent();
- * tinymce.EditorManager.activeEditor.selection.getContent();
- */
- activeEditor: null,
- setup: function() {
- var self = this, baseURL, documentBaseURL, suffix = "", preInit, src;
- // Get base URL for the current document
- documentBaseURL = URI.getDocumentBaseUrl(document.location);
- // Check if the URL is a document based format like: http://site/dir/file and file:///
- // leave other formats like applewebdata://... intact
- if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
- documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
- if (!/[\/\\]$/.test(documentBaseURL)) {
- documentBaseURL += '/';
- }
- }
- // If tinymce is defined and has a base use that or use the old tinyMCEPreInit
- preInit = window.tinymce || window.tinyMCEPreInit;
- if (preInit) {
- baseURL = preInit.base || preInit.baseURL;
- suffix = preInit.suffix;
- } else {
- // Get base where the tinymce script is located
- var scripts = document.getElementsByTagName('script');
- for (var i = 0; i < scripts.length; i++) {
- src = scripts[i].src;
- // Script types supported:
- // tinymce.js tinymce.min.js tinymce.dev.js
- // tinymce.jquery.js tinymce.jquery.min.js tinymce.jquery.dev.js
- // tinymce.full.js tinymce.full.min.js tinymce.full.dev.js
- var srcScript = src.substring(src.lastIndexOf('/'));
- if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
- if (srcScript.indexOf('.min') != -1) {
- suffix = '.min';
- }
- baseURL = src.substring(0, src.lastIndexOf('/'));
- break;
- }
- }
- // We didn't find any baseURL by looking at the script elements
- // Try to use the document.currentScript as a fallback
- if (!baseURL && document.currentScript) {
- src = document.currentScript.src;
- if (src.indexOf('.min') != -1) {
- suffix = '.min';
- }
- baseURL = src.substring(0, src.lastIndexOf('/'));
- }
- }
- /**
- * Base URL where the root directory if TinyMCE is located.
- *
- * @property baseURL
- * @type String
- */
- self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
- /**
- * Document base URL where the current document is located.
- *
- * @property documentBaseURL
- * @type String
- */
- self.documentBaseURL = documentBaseURL;
- /**
- * Absolute baseURI for the installation path of TinyMCE.
- *
- * @property baseURI
- * @type tinymce.util.URI
- */
- self.baseURI = new URI(self.baseURL);
- /**
- * Current suffix to add to each plugin/theme that gets loaded for example ".min".
- *
- * @property suffix
- * @type String
- */
- self.suffix = suffix;
- self.focusManager = new FocusManager(self);
- },
- /**
- * Overrides the default settings for editor instances.
- *
- * @method overrideDefaults
- * @param {Object} defaultSettings Defaults settings object.
- */
- overrideDefaults: function(defaultSettings) {
- var baseUrl, suffix;
- baseUrl = defaultSettings.base_url;
- if (baseUrl) {
- this.baseURL = new URI(this.documentBaseURL).toAbsolute(baseUrl.replace(/\/+$/, ''));
- this.baseURI = new URI(this.baseURL);
- }
- suffix = defaultSettings.suffix;
- if (defaultSettings.suffix) {
- this.suffix = suffix;
- }
- this.defaultSettings = defaultSettings;
- },
- /**
- * Initializes a set of editors. This method will create editors based on various settings.
- *
- * @method init
- * @param {Object} settings Settings object to be passed to each editor instance.
- * @return {tinymce.util.Promise} Promise that gets resolved with an array of editors when all editor instances are initialized.
- * @example
- * // Initializes a editor using the longer method
- * tinymce.EditorManager.init({
- * some_settings : 'some value'
- * });
- *
- * // Initializes a editor instance using the shorter version and with a promise
- * tinymce.init({
- * some_settings : 'some value'
- * }).then(function(editors) {
- * ...
- * });
- */
- init: function(settings) {
- var self = this, result, invalidInlineTargets;
- invalidInlineTargets = Tools.makeMap(
- 'area base basefont br col frame hr img input isindex link meta param embed source wbr track ' +
- 'colgroup option tbody tfoot thead tr script noscript style textarea video audio iframe object menu',
- ' '
- );
- function isInvalidInlineTarget(settings, elm) {
- return settings.inline && elm.tagName.toLowerCase() in invalidInlineTargets;
- }
- function report(msg, elm) {
- // Log in a non test environment
- if (window.console && !window.test) {
- window.console.log(msg, elm);
- }
- }
- function createId(elm) {
- var id = elm.id;
- // Use element id, or unique name or generate a unique id
- if (!id) {
- id = elm.name;
- if (id && !DOM.get(id)) {
- id = elm.name;
- } else {
- // Generate unique name
- id = DOM.uniqueId();
- }
- elm.setAttribute('id', id);
- }
- return id;
- }
- function execCallback(name) {
- var callback = settings[name];
- if (!callback) {
- return;
- }
- return callback.apply(self, Array.prototype.slice.call(arguments, 2));
- }
- function hasClass(elm, className) {
- return className.constructor === RegExp ? className.test(elm.className) : DOM.hasClass(elm, className);
- }
- function findTargets(settings) {
- var l, targets = [];
- if (settings.types) {
- each(settings.types, function(type) {
- targets = targets.concat(DOM.select(type.selector));
- });
- return targets;
- } else if (settings.selector) {
- return DOM.select(settings.selector);
- } else if (settings.target) {
- return [settings.target];
- }
- // Fallback to old setting
- switch (settings.mode) {
- case "exact":
- l = settings.elements || '';
- if (l.length > 0) {
- each(explode(l), function(id) {
- var elm;
- if ((elm = DOM.get(id))) {
- targets.push(elm);
- } else {
- each(document.forms, function(f) {
- each(f.elements, function(e) {
- if (e.name === id) {
- id = 'mce_editor_' + instanceCounter++;
- DOM.setAttrib(e, 'id', id);
- targets.push(e);
- }
- });
- });
- }
- });
- }
- break;
- case "textareas":
- case "specific_textareas":
- each(DOM.select('textarea'), function(elm) {
- if (settings.editor_deselector && hasClass(elm, settings.editor_deselector)) {
- return;
- }
- if (!settings.editor_selector || hasClass(elm, settings.editor_selector)) {
- targets.push(elm);
- }
- });
- break;
- }
- return targets;
- }
- var provideResults = function(editors) {
- result = editors;
- };
- function initEditors() {
- var initCount = 0, editors = [], targets;
- function createEditor(id, settings, targetElm) {
- var editor = new Editor(id, settings, self);
- editors.push(editor);
- editor.on('init', function() {
- if (++initCount === targets.length) {
- provideResults(editors);
- }
- });
- editor.targetElm = editor.targetElm || targetElm;
- editor.render();
- }
- DOM.unbind(window, 'ready', initEditors);
- execCallback('onpageload');
- targets = $.unique(findTargets(settings));
- // TODO: Deprecate this one
- if (settings.types) {
- each(settings.types, function(type) {
- Tools.each(targets, function(elm) {
- if (DOM.is(elm, type.selector)) {
- createEditor(createId(elm), extend({}, settings, type), elm);
- return false;
- }
- return true;
- });
- });
- return;
- }
- Tools.each(targets, function(elm) {
- purgeDestroyedEditor(self.get(elm.id));
- });
- targets = Tools.grep(targets, function(elm) {
- return !self.get(elm.id);
- });
- each(targets, function(elm) {
- if (isInvalidInlineTarget(settings, elm)) {
- report('Could not initialize inline editor on invalid inline target element', elm);
- } else {
- createEditor(createId(elm), settings, elm);
- }
- });
- }
- self.settings = settings;
- DOM.bind(window, 'ready', initEditors);
- return new Promise(function(resolve) {
- if (result) {
- resolve(result);
- } else {
- provideResults = function(editors) {
- resolve(editors);
- };
- }
- });
- },
- /**
- * Returns a editor instance by id.
- *
- * @method get
- * @param {String/Number} id Editor instance id or index to return.
- * @return {tinymce.Editor} Editor instance to return.
- * @example
- * // Adds an onclick event to an editor by id (shorter version)
- * tinymce.get('mytextbox').on('click', function(e) {
- * ed.windowManager.alert('Hello world!');
- * });
- *
- * // Adds an onclick event to an editor by id (longer version)
- * tinymce.EditorManager.get('mytextbox').on('click', function(e) {
- * ed.windowManager.alert('Hello world!');
- * });
- */
- get: function(id) {
- if (!arguments.length) {
- return this.editors;
- }
- return id in this.editors ? this.editors[id] : null;
- },
- /**
- * Adds an editor instance to the editor collection. This will also set it as the active editor.
- *
- * @method add
- * @param {tinymce.Editor} editor Editor instance to add to the collection.
- * @return {tinymce.Editor} The same instance that got passed in.
- */
- add: function(editor) {
- var self = this, editors = self.editors;
- // Add named and index editor instance
- editors[editor.id] = editor;
- editors.push(editor);
- toggleGlobalEvents(editors, true);
- // Doesn't call setActive method since we don't want
- // to fire a bunch of activate/deactivate calls while initializing
- self.activeEditor = editor;
- /**
- * Fires when an editor is added to the EditorManager collection.
- *
- * @event AddEditor
- * @param {Object} e Event arguments.
- */
- self.fire('AddEditor', {editor: editor});
- if (!beforeUnloadDelegate) {
- beforeUnloadDelegate = function() {
- self.fire('BeforeUnload');
- };
- DOM.bind(window, 'beforeunload', beforeUnloadDelegate);
- }
- return editor;
- },
- /**
- * Creates an editor instance and adds it to the EditorManager collection.
- *
- * @method createEditor
- * @param {String} id Instance id to use for editor.
- * @param {Object} settings Editor instance settings.
- * @return {tinymce.Editor} Editor instance that got created.
- */
- createEditor: function(id, settings) {
- return this.add(new Editor(id, settings, this));
- },
- /**
- * Removes a editor or editors form page.
- *
- * @example
- * // Remove all editors bound to divs
- * tinymce.remove('div');
- *
- * // Remove all editors bound to textareas
- * tinymce.remove('textarea');
- *
- * // Remove all editors
- * tinymce.remove();
- *
- * // Remove specific instance by id
- * tinymce.remove('#id');
- *
- * @method remove
- * @param {tinymce.Editor/String/Object} [selector] CSS selector or editor instance to remove.
- * @return {tinymce.Editor} The editor that got passed in will be return if it was found otherwise null.
- */
- remove: function(selector) {
- var self = this, i, editors = self.editors, editor;
- // Remove all editors
- if (!selector) {
- for (i = editors.length - 1; i >= 0; i--) {
- self.remove(editors[i]);
- }
- return;
- }
- // Remove editors by selector
- if (typeof selector == "string") {
- selector = selector.selector || selector;
- each(DOM.select(selector), function(elm) {
- editor = editors[elm.id];
- if (editor) {
- self.remove(editor);
- }
- });
- return;
- }
- // Remove specific editor
- editor = selector;
- // Not in the collection
- if (!editors[editor.id]) {
- return null;
- }
- /**
- * Fires when an editor is removed from EditorManager collection.
- *
- * @event RemoveEditor
- * @param {Object} e Event arguments.
- */
- if (removeEditorFromList(editor)) {
- self.fire('RemoveEditor', {editor: editor});
- }
- if (!editors.length) {
- DOM.unbind(window, 'beforeunload', beforeUnloadDelegate);
- }
- editor.remove();
- toggleGlobalEvents(editors, editors.length > 0);
- return editor;
- },
- /**
- * Executes a specific command on the currently active editor.
- *
- * @method execCommand
- * @param {String} cmd Command to perform for example Bold.
- * @param {Boolean} ui Optional boolean state if a UI should be presented for the command or not.
- * @param {String} value Optional value parameter like for example an URL to a link.
- * @return {Boolean} true/false if the command was executed or not.
- */
- execCommand: function(cmd, ui, value) {
- var self = this, editor = self.get(value);
- // Manager commands
- switch (cmd) {
- case "mceAddEditor":
- if (!self.get(value)) {
- new Editor(value, self.settings, self).render();
- }
- return true;
- case "mceRemoveEditor":
- if (editor) {
- editor.remove();
- }
- return true;
- case 'mceToggleEditor':
- if (!editor) {
- self.execCommand('mceAddEditor', 0, value);
- return true;
- }
- if (editor.isHidden()) {
- editor.show();
- } else {
- editor.hide();
- }
- return true;
- }
- // Run command on active editor
- if (self.activeEditor) {
- return self.activeEditor.execCommand(cmd, ui, value);
- }
- return false;
- },
- /**
- * Calls the save method on all editor instances in the collection. This can be useful when a form is to be submitted.
- *
- * @method triggerSave
- * @example
- * // Saves all contents
- * tinyMCE.triggerSave();
- */
- triggerSave: function() {
- each(this.editors, function(editor) {
- editor.save();
- });
- },
- /**
- * Adds a language pack, this gets called by the loaded language files like en.js.
- *
- * @method addI18n
- * @param {String} code Optional language code.
- * @param {Object} items Name/value object with translations.
- */
- addI18n: function(code, items) {
- I18n.add(code, items);
- },
- /**
- * Translates the specified string using the language pack items.
- *
- * @method translate
- * @param {String/Array/Object} text String to translate
- * @return {String} Translated string.
- */
- translate: function(text) {
- return I18n.translate(text);
- },
- /**
- * Sets the active editor instance and fires the deactivate/activate events.
- *
- * @method setActive
- * @param {tinymce.Editor} editor Editor instance to set as the active instance.
- */
- setActive: function(editor) {
- var activeEditor = this.activeEditor;
- if (this.activeEditor != editor) {
- if (activeEditor) {
- activeEditor.fire('deactivate', {relatedTarget: editor});
- }
- editor.fire('activate', {relatedTarget: activeEditor});
- }
- this.activeEditor = editor;
- }
- };
- extend(EditorManager, Observable);
- EditorManager.setup();
- // Export EditorManager as tinymce/tinymce in global namespace
- window.tinymce = window.tinyMCE = EditorManager;
- return EditorManager;
- });
- // Included from: js/tinymce/classes/LegacyInput.js
- /**
- * LegacyInput.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Converts legacy input to modern HTML.
- *
- * @class tinymce.LegacyInput
- * @private
- */
- define("tinymce/LegacyInput", [
- "tinymce/EditorManager",
- "tinymce/util/Tools"
- ], function(EditorManager, Tools) {
- var each = Tools.each, explode = Tools.explode;
- EditorManager.on('AddEditor', function(e) {
- var editor = e.editor;
- editor.on('preInit', function() {
- var filters, fontSizes, dom, settings = editor.settings;
- function replaceWithSpan(node, styles) {
- each(styles, function(value, name) {
- if (value) {
- dom.setStyle(node, name, value);
- }
- });
- dom.rename(node, 'span');
- }
- function convert(e) {
- dom = editor.dom;
- if (settings.convert_fonts_to_spans) {
- each(dom.select('font,u,strike', e.node), function(node) {
- filters[node.nodeName.toLowerCase()](dom, node);
- });
- }
- }
- if (settings.inline_styles) {
- fontSizes = explode(settings.font_size_legacy_values);
- filters = {
- font: function(dom, node) {
- replaceWithSpan(node, {
- backgroundColor: node.style.backgroundColor,
- color: node.color,
- fontFamily: node.face,
- fontSize: fontSizes[parseInt(node.size, 10) - 1]
- });
- },
- u: function(dom, node) {
- // HTML5 allows U element
- if (editor.settings.schema === "html4") {
- replaceWithSpan(node, {
- textDecoration: 'underline'
- });
- }
- },
- strike: function(dom, node) {
- replaceWithSpan(node, {
- textDecoration: 'line-through'
- });
- }
- };
- editor.on('PreProcess SetContent', convert);
- }
- });
- });
- });
- // Included from: js/tinymce/classes/util/XHR.js
- /**
- * XHR.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class enables you to send XMLHTTPRequests cross browser.
- * @class tinymce.util.XHR
- * @mixes tinymce.util.Observable
- * @static
- * @example
- * // Sends a low level Ajax request
- * tinymce.util.XHR.send({
- * url: 'someurl',
- * success: function(text) {
- * console.debug(text);
- * }
- * });
- *
- * // Add custom header to XHR request
- * tinymce.util.XHR.on('beforeSend', function(e) {
- * e.xhr.setRequestHeader('X-Requested-With', 'Something');
- * });
- */
- define("tinymce/util/XHR", [
- "tinymce/util/Observable",
- "tinymce/util/Tools"
- ], function(Observable, Tools) {
- var XHR = {
- /**
- * Sends a XMLHTTPRequest.
- * Consult the Wiki for details on what settings this method takes.
- *
- * @method send
- * @param {Object} settings Object will target URL, callbacks and other info needed to make the request.
- */
- send: function(settings) {
- var xhr, count = 0;
- function ready() {
- if (!settings.async || xhr.readyState == 4 || count++ > 10000) {
- if (settings.success && count < 10000 && xhr.status == 200) {
- settings.success.call(settings.success_scope, '' + xhr.responseText, xhr, settings);
- } else if (settings.error) {
- settings.error.call(settings.error_scope, count > 10000 ? 'TIMED_OUT' : 'GENERAL', xhr, settings);
- }
- xhr = null;
- } else {
- setTimeout(ready, 10);
- }
- }
- // Default settings
- settings.scope = settings.scope || this;
- settings.success_scope = settings.success_scope || settings.scope;
- settings.error_scope = settings.error_scope || settings.scope;
- settings.async = settings.async === false ? false : true;
- settings.data = settings.data || '';
- XHR.fire('beforeInitialize', {settings: settings});
- xhr = new XMLHttpRequest();
- if (xhr) {
- if (xhr.overrideMimeType) {
- xhr.overrideMimeType(settings.content_type);
- }
- xhr.open(settings.type || (settings.data ? 'POST' : 'GET'), settings.url, settings.async);
- if (settings.crossDomain) {
- xhr.withCredentials = true;
- }
- if (settings.content_type) {
- xhr.setRequestHeader('Content-Type', settings.content_type);
- }
- if (settings.requestheaders) {
- Tools.each(settings.requestheaders, function(header) {
- xhr.setRequestHeader(header.key, header.value);
- });
- }
- xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
- xhr = XHR.fire('beforeSend', {xhr: xhr, settings: settings}).xhr;
- xhr.send(settings.data);
- // Syncronous request
- if (!settings.async) {
- return ready();
- }
- // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
- setTimeout(ready, 10);
- }
- }
- };
- Tools.extend(XHR, Observable);
- return XHR;
- });
- // Included from: js/tinymce/classes/util/JSON.js
- /**
- * JSON.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * JSON parser and serializer class.
- *
- * @class tinymce.util.JSON
- * @static
- * @example
- * // JSON parse a string into an object
- * var obj = tinymce.util.JSON.parse(somestring);
- *
- * // JSON serialize a object into an string
- * var str = tinymce.util.JSON.serialize(obj);
- */
- define("tinymce/util/JSON", [], function() {
- function serialize(o, quote) {
- var i, v, t, name;
- quote = quote || '"';
- if (o === null) {
- return 'null';
- }
- t = typeof o;
- if (t == 'string') {
- v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
- /*eslint no-control-regex:0 */
- return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
- // Make sure single quotes never get encoded inside double quotes for JSON compatibility
- if (quote === '"' && a === "'") {
- return a;
- }
- i = v.indexOf(b);
- if (i + 1) {
- return '\\' + v.charAt(i + 1);
- }
- a = b.charCodeAt().toString(16);
- return '\\u' + '0000'.substring(a.length) + a;
- }) + quote;
- }
- if (t == 'object') {
- if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
- for (i = 0, v = '['; i < o.length; i++) {
- v += (i > 0 ? ',' : '') + serialize(o[i], quote);
- }
- return v + ']';
- }
- v = '{';
- for (name in o) {
- if (o.hasOwnProperty(name)) {
- v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name +
- quote + ':' + serialize(o[name], quote) : '';
- }
- }
- return v + '}';
- }
- return '' + o;
- }
- return {
- /**
- * Serializes the specified object as a JSON string.
- *
- * @method serialize
- * @param {Object} obj Object to serialize as a JSON string.
- * @param {String} quote Optional quote string defaults to ".
- * @return {string} JSON string serialized from input.
- */
- serialize: serialize,
- /**
- * Unserializes/parses the specified JSON string into a object.
- *
- * @method parse
- * @param {string} s JSON String to parse into a JavaScript object.
- * @return {Object} Object from input JSON string or undefined if it failed.
- */
- parse: function(text) {
- try {
- // Trick uglify JS
- return window[String.fromCharCode(101) + 'val']('(' + text + ')');
- } catch (ex) {
- // Ignore
- }
- }
- /**#@-*/
- };
- });
- // Included from: js/tinymce/classes/util/JSONRequest.js
- /**
- * JSONRequest.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class enables you to use JSON-RPC to call backend methods.
- *
- * @class tinymce.util.JSONRequest
- * @example
- * var json = new tinymce.util.JSONRequest({
- * url: 'somebackend.php'
- * });
- *
- * // Send RPC call 1
- * json.send({
- * method: 'someMethod1',
- * params: ['a', 'b'],
- * success: function(result) {
- * console.dir(result);
- * }
- * });
- *
- * // Send RPC call 2
- * json.send({
- * method: 'someMethod2',
- * params: ['a', 'b'],
- * success: function(result) {
- * console.dir(result);
- * }
- * });
- */
- define("tinymce/util/JSONRequest", [
- "tinymce/util/JSON",
- "tinymce/util/XHR",
- "tinymce/util/Tools"
- ], function(JSON, XHR, Tools) {
- var extend = Tools.extend;
- function JSONRequest(settings) {
- this.settings = extend({}, settings);
- this.count = 0;
- }
- /**
- * Simple helper function to send a JSON-RPC request without the need to initialize an object.
- * Consult the Wiki API documentation for more details on what you can pass to this function.
- *
- * @method sendRPC
- * @static
- * @param {Object} o Call object where there are three field id, method and params this object should also contain callbacks etc.
- */
- JSONRequest.sendRPC = function(o) {
- return new JSONRequest().send(o);
- };
- JSONRequest.prototype = {
- /**
- * Sends a JSON-RPC call. Consult the Wiki API documentation for more details on what you can pass to this function.
- *
- * @method send
- * @param {Object} args Call object where there are three field id, method and params this object should also contain callbacks etc.
- */
- send: function(args) {
- var ecb = args.error, scb = args.success;
- args = extend(this.settings, args);
- args.success = function(c, x) {
- c = JSON.parse(c);
- if (typeof c == 'undefined') {
- c = {
- error: 'JSON Parse error.'
- };
- }
- if (c.error) {
- ecb.call(args.error_scope || args.scope, c.error, x);
- } else {
- scb.call(args.success_scope || args.scope, c.result);
- }
- };
- args.error = function(ty, x) {
- if (ecb) {
- ecb.call(args.error_scope || args.scope, ty, x);
- }
- };
- args.data = JSON.serialize({
- id: args.id || 'c' + (this.count++),
- method: args.method,
- params: args.params
- });
- // JSON content type for Ruby on rails. Bug: #1883287
- args.content_type = 'application/json';
- XHR.send(args);
- }
- };
- return JSONRequest;
- });
- // Included from: js/tinymce/classes/util/JSONP.js
- /**
- * JSONP.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- define("tinymce/util/JSONP", [
- "tinymce/dom/DOMUtils"
- ], function(DOMUtils) {
- return {
- callbacks: {},
- count: 0,
- send: function(settings) {
- var self = this, dom = DOMUtils.DOM, count = settings.count !== undefined ? settings.count : self.count;
- var id = 'tinymce_jsonp_' + count;
- self.callbacks[count] = function(json) {
- dom.remove(id);
- delete self.callbacks[count];
- settings.callback(json);
- };
- dom.add(dom.doc.body, 'script', {
- id: id,
- src: settings.url,
- type: 'text/javascript'
- });
- self.count++;
- }
- };
- });
- // Included from: js/tinymce/classes/util/LocalStorage.js
- /**
- * LocalStorage.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class will simulate LocalStorage on IE 7 and return the native version on modern browsers.
- * Storage is done using userData on IE 7 and a special serialization format. The format is designed
- * to be as small as possible by making sure that the keys and values doesn't need to be encoded. This
- * makes it possible to store for example HTML data.
- *
- * Storage format for userData:
- * <base 32 key length>,<key string>,<base 32 value length>,<value>,...
- *
- * For example this data key1=value1,key2=value2 would be:
- * 4,key1,6,value1,4,key2,6,value2
- *
- * @class tinymce.util.LocalStorage
- * @static
- * @version 4.0
- * @example
- * tinymce.util.LocalStorage.setItem('key', 'value');
- * var value = tinymce.util.LocalStorage.getItem('key');
- */
- define("tinymce/util/LocalStorage", [], function() {
- var LocalStorage, storageElm, items, keys, userDataKey, hasOldIEDataSupport;
- // Check for native support
- try {
- if (window.localStorage) {
- return localStorage;
- }
- } catch (ex) {
- // Ignore
- }
- userDataKey = "tinymce";
- storageElm = document.documentElement;
- hasOldIEDataSupport = !!storageElm.addBehavior;
- if (hasOldIEDataSupport) {
- storageElm.addBehavior('#default#userData');
- }
- /**
- * Gets the keys names and updates LocalStorage.length property. Since IE7 doesn't have any getters/setters.
- */
- function updateKeys() {
- keys = [];
- for (var key in items) {
- keys.push(key);
- }
- LocalStorage.length = keys.length;
- }
- /**
- * Loads the userData string and parses it into the items structure.
- */
- function load() {
- var key, data, value, pos = 0;
- items = {};
- // localStorage can be disabled on WebKit/Gecko so make a dummy storage
- if (!hasOldIEDataSupport) {
- return;
- }
- function next(end) {
- var value, nextPos;
- nextPos = end !== undefined ? pos + end : data.indexOf(',', pos);
- if (nextPos === -1 || nextPos > data.length) {
- return null;
- }
- value = data.substring(pos, nextPos);
- pos = nextPos + 1;
- return value;
- }
- storageElm.load(userDataKey);
- data = storageElm.getAttribute(userDataKey) || '';
- do {
- var offset = next();
- if (offset === null) {
- break;
- }
- key = next(parseInt(offset, 32) || 0);
- if (key !== null) {
- offset = next();
- if (offset === null) {
- break;
- }
- value = next(parseInt(offset, 32) || 0);
- if (key) {
- items[key] = value;
- }
- }
- } while (key !== null);
- updateKeys();
- }
- /**
- * Saves the items structure into a the userData format.
- */
- function save() {
- var value, data = '';
- // localStorage can be disabled on WebKit/Gecko so make a dummy storage
- if (!hasOldIEDataSupport) {
- return;
- }
- for (var key in items) {
- value = items[key];
- data += (data ? ',' : '') + key.length.toString(32) + ',' + key + ',' + value.length.toString(32) + ',' + value;
- }
- storageElm.setAttribute(userDataKey, data);
- try {
- storageElm.save(userDataKey);
- } catch (ex) {
- // Ignore disk full
- }
- updateKeys();
- }
- LocalStorage = {
- /**
- * Length of the number of items in storage.
- *
- * @property length
- * @type Number
- * @return {Number} Number of items in storage.
- */
- //length:0,
- /**
- * Returns the key name by index.
- *
- * @method key
- * @param {Number} index Index of key to return.
- * @return {String} Key value or null if it wasn't found.
- */
- key: function(index) {
- return keys[index];
- },
- /**
- * Returns the value if the specified key or null if it wasn't found.
- *
- * @method getItem
- * @param {String} key Key of item to retrieve.
- * @return {String} Value of the specified item or null if it wasn't found.
- */
- getItem: function(key) {
- return key in items ? items[key] : null;
- },
- /**
- * Sets the value of the specified item by it's key.
- *
- * @method setItem
- * @param {String} key Key of the item to set.
- * @param {String} value Value of the item to set.
- */
- setItem: function(key, value) {
- items[key] = "" + value;
- save();
- },
- /**
- * Removes the specified item by key.
- *
- * @method removeItem
- * @param {String} key Key of item to remove.
- */
- removeItem: function(key) {
- delete items[key];
- save();
- },
- /**
- * Removes all items.
- *
- * @method clear
- */
- clear: function() {
- items = {};
- save();
- }
- };
- load();
- return LocalStorage;
- });
- // Included from: js/tinymce/classes/Compat.js
- /**
- * Compat.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * TinyMCE core class.
- *
- * @static
- * @class tinymce
- * @borrow-members tinymce.EditorManager
- * @borrow-members tinymce.util.Tools
- */
- define("tinymce/Compat", [
- "tinymce/dom/DOMUtils",
- "tinymce/dom/EventUtils",
- "tinymce/dom/ScriptLoader",
- "tinymce/AddOnManager",
- "tinymce/util/Tools",
- "tinymce/Env"
- ], function(DOMUtils, EventUtils, ScriptLoader, AddOnManager, Tools, Env) {
- var tinymce = window.tinymce;
- /**
- * @property {tinymce.dom.DOMUtils} DOM Global DOM instance.
- * @property {tinymce.dom.ScriptLoader} ScriptLoader Global ScriptLoader instance.
- * @property {tinymce.AddOnManager} PluginManager Global PluginManager instance.
- * @property {tinymce.AddOnManager} ThemeManager Global ThemeManager instance.
- */
- tinymce.DOM = DOMUtils.DOM;
- tinymce.ScriptLoader = ScriptLoader.ScriptLoader;
- tinymce.PluginManager = AddOnManager.PluginManager;
- tinymce.ThemeManager = AddOnManager.ThemeManager;
- tinymce.dom = tinymce.dom || {};
- tinymce.dom.Event = EventUtils.Event;
- Tools.each(Tools, function(func, key) {
- tinymce[key] = func;
- });
- Tools.each('isOpera isWebKit isIE isGecko isMac'.split(' '), function(name) {
- tinymce[name] = Env[name.substr(2).toLowerCase()];
- });
- return {};
- });
- // Describe the different namespaces
- /**
- * Root level namespace this contains classes directly related to the TinyMCE editor.
- *
- * @namespace tinymce
- */
- /**
- * Contains classes for handling the browsers DOM.
- *
- * @namespace tinymce.dom
- */
- /**
- * Contains html parser and serializer logic.
- *
- * @namespace tinymce.html
- */
- /**
- * Contains the different UI types such as buttons, listboxes etc.
- *
- * @namespace tinymce.ui
- */
- /**
- * Contains various utility classes such as json parser, cookies etc.
- *
- * @namespace tinymce.util
- */
- /**
- * Contains modules to handle data binding.
- *
- * @namespace tinymce.data
- */
- // Included from: js/tinymce/classes/ui/Layout.js
- /**
- * Layout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Base layout manager class.
- *
- * @class tinymce.ui.Layout
- */
- define("tinymce/ui/Layout", [
- "tinymce/util/Class",
- "tinymce/util/Tools"
- ], function(Class, Tools) {
- "use strict";
- return Class.extend({
- Defaults: {
- firstControlClass: 'first',
- lastControlClass: 'last'
- },
- /**
- * Constructs a layout instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- this.settings = Tools.extend({}, this.Defaults, settings);
- },
- /**
- * This method gets invoked before the layout renders the controls.
- *
- * @method preRender
- * @param {tinymce.ui.Container} container Container instance to preRender.
- */
- preRender: function(container) {
- container.bodyClasses.add(this.settings.containerClass);
- },
- /**
- * Applies layout classes to the container.
- *
- * @private
- */
- applyClasses: function(items) {
- var self = this, settings = self.settings, firstClass, lastClass, firstItem, lastItem;
- firstClass = settings.firstControlClass;
- lastClass = settings.lastControlClass;
- items.each(function(item) {
- item.classes.remove(firstClass).remove(lastClass).add(settings.controlClass);
- if (item.visible()) {
- if (!firstItem) {
- firstItem = item;
- }
- lastItem = item;
- }
- });
- if (firstItem) {
- firstItem.classes.add(firstClass);
- }
- if (lastItem) {
- lastItem.classes.add(lastClass);
- }
- },
- /**
- * Renders the specified container and any layout specific HTML.
- *
- * @method renderHtml
- * @param {tinymce.ui.Container} container Container to render HTML for.
- */
- renderHtml: function(container) {
- var self = this, html = '';
- self.applyClasses(container.items());
- container.items().each(function(item) {
- html += item.renderHtml();
- });
- return html;
- },
- /**
- * Recalculates the positions of the controls in the specified container.
- *
- * @method recalc
- * @param {tinymce.ui.Container} container Container instance to recalc.
- */
- recalc: function() {
- },
- /**
- * This method gets invoked after the layout renders the controls.
- *
- * @method postRender
- * @param {tinymce.ui.Container} container Container instance to postRender.
- */
- postRender: function() {
- },
- isNative: function() {
- return false;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/AbsoluteLayout.js
- /**
- * AbsoluteLayout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * LayoutManager for absolute positioning. This layout manager is more of
- * a base class for other layouts but can be created and used directly.
- *
- * @-x-less AbsoluteLayout.less
- * @class tinymce.ui.AbsoluteLayout
- * @extends tinymce.ui.Layout
- */
- define("tinymce/ui/AbsoluteLayout", [
- "tinymce/ui/Layout"
- ], function(Layout) {
- "use strict";
- return Layout.extend({
- Defaults: {
- containerClass: 'abs-layout',
- controlClass: 'abs-layout-item'
- },
- /**
- * Recalculates the positions of the controls in the specified container.
- *
- * @method recalc
- * @param {tinymce.ui.Container} container Container instance to recalc.
- */
- recalc: function(container) {
- container.items().filter(':visible').each(function(ctrl) {
- var settings = ctrl.settings;
- ctrl.layoutRect({
- x: settings.x,
- y: settings.y,
- w: settings.w,
- h: settings.h
- });
- if (ctrl.recalc) {
- ctrl.recalc();
- }
- });
- },
- /**
- * Renders the specified container and any layout specific HTML.
- *
- * @method renderHtml
- * @param {tinymce.ui.Container} container Container to render HTML for.
- */
- renderHtml: function(container) {
- return '<div id="' + container._id + '-absend" class="' + container.classPrefix + 'abs-end"></div>' + this._super(container);
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Button.js
- /**
- * Button.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is used to create buttons. You can create them directly or through the Factory.
- *
- * @example
- * // Create and render a button to the body element
- * tinymce.ui.Factory.create({
- * type: 'button',
- * text: 'My button'
- * }).renderTo(document.body);
- *
- * @-x-less Button.less
- * @class tinymce.ui.Button
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Button", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- return Widget.extend({
- Defaults: {
- classes: "widget btn",
- role: "button"
- },
- /**
- * Constructs a new button instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} size Size of the button small|medium|large.
- * @setting {String} image Image to use for icon.
- * @setting {String} icon Icon to use for button.
- */
- init: function(settings) {
- var self = this, size;
- self._super(settings);
- settings = self.settings;
- size = self.settings.size;
- self.on('click mousedown', function(e) {
- e.preventDefault();
- });
- self.on('touchstart', function(e) {
- self.fire('click', e);
- e.preventDefault();
- });
- if (settings.subtype) {
- self.classes.add(settings.subtype);
- }
- if (size) {
- self.classes.add('btn-' + size);
- }
- if (settings.icon) {
- self.icon(settings.icon);
- }
- },
- /**
- * Sets/gets the current button icon.
- *
- * @method icon
- * @param {String} [icon] New icon identifier.
- * @return {String|tinymce.ui.MenuButton} Current icon or current MenuButton instance.
- */
- icon: function(icon) {
- if (!arguments.length) {
- return this.state.get('icon');
- }
- this.state.set('icon', icon);
- return this;
- },
- /**
- * Repaints the button for example after it's been resizes by a layout engine.
- *
- * @method repaint
- */
- repaint: function() {
- var btnElm = this.getEl().firstChild,
- btnStyle;
- if (btnElm) {
- btnStyle = btnElm.style;
- btnStyle.width = btnStyle.height = "100%";
- }
- this._super();
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix;
- var icon = self.state.get('icon'), image, text = self.state.get('text'), textHtml = '';
- image = self.settings.image;
- if (image) {
- icon = 'none';
- // Support for [high dpi, low dpi] image sources
- if (typeof image != "string") {
- image = window.getSelection ? image[0] : image[1];
- }
- image = ' style="background-image: url(\'' + image + '\')"';
- } else {
- image = '';
- }
- if (text) {
- self.classes.add('btn-has-text');
- textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
- }
- icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
- return (
- '<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' +
- '<button role="presentation" type="button" tabindex="-1">' +
- (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
- textHtml +
- '</button>' +
- '</div>'
- );
- },
- bindStates: function() {
- var self = this, $ = self.$, textCls = self.classPrefix + 'txt';
- function setButtonText(text) {
- var $span = $('span.' + textCls, self.getEl());
- if (text) {
- if (!$span[0]) {
- $('button:first', self.getEl()).append('<span class="' + textCls + '"></span>');
- $span = $('span.' + textCls, self.getEl());
- }
- $span.html(self.encode(text));
- } else {
- $span.remove();
- }
- self.classes.toggle('btn-has-text', !!text);
- }
- self.state.on('change:text', function(e) {
- setButtonText(e.value);
- });
- self.state.on('change:icon', function(e) {
- var icon = e.value, prefix = self.classPrefix;
- self.settings.icon = icon;
- icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
- var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
- if (icon) {
- if (!iconElm || iconElm != btnElm.firstChild) {
- iconElm = document.createElement('i');
- btnElm.insertBefore(iconElm, btnElm.firstChild);
- }
- iconElm.className = icon;
- } else if (iconElm) {
- btnElm.removeChild(iconElm);
- }
- setButtonText(self.state.get('text'));
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ButtonGroup.js
- /**
- * ButtonGroup.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This control enables you to put multiple buttons into a group. This is
- * useful when you want to combine similar toolbar buttons into a group.
- *
- * @example
- * // Create and render a buttongroup with two buttons to the body element
- * tinymce.ui.Factory.create({
- * type: 'buttongroup',
- * items: [
- * {text: 'Button A'},
- * {text: 'Button B'}
- * ]
- * }).renderTo(document.body);
- *
- * @-x-less ButtonGroup.less
- * @class tinymce.ui.ButtonGroup
- * @extends tinymce.ui.Container
- */
- define("tinymce/ui/ButtonGroup", [
- "tinymce/ui/Container"
- ], function(Container) {
- "use strict";
- return Container.extend({
- Defaults: {
- defaultType: 'button',
- role: 'group'
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout;
- self.classes.add('btn-group');
- self.preRender();
- layout.preRender(self);
- return (
- '<div id="' + self._id + '" class="' + self.classes + '">' +
- '<div id="' + self._id + '-body">' +
- (self.settings.html || '') + layout.renderHtml(self) +
- '</div>' +
- '</div>'
- );
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Checkbox.js
- /**
- * Checkbox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This control creates a custom checkbox.
- *
- * @example
- * // Create and render a checkbox to the body element
- * tinymce.ui.Factory.create({
- * type: 'checkbox',
- * checked: true,
- * text: 'My checkbox'
- * }).renderTo(document.body);
- *
- * @-x-less Checkbox.less
- * @class tinymce.ui.Checkbox
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Checkbox", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- return Widget.extend({
- Defaults: {
- classes: "checkbox",
- role: "checkbox",
- checked: false
- },
- /**
- * Constructs a new Checkbox instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Boolean} checked True if the checkbox should be checked by default.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- self.on('click mousedown', function(e) {
- e.preventDefault();
- });
- self.on('click', function(e) {
- e.preventDefault();
- if (!self.disabled()) {
- self.checked(!self.checked());
- }
- });
- self.checked(self.settings.checked);
- },
- /**
- * Getter/setter function for the checked state.
- *
- * @method checked
- * @param {Boolean} [state] State to be set.
- * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
- */
- checked: function(state) {
- if (!arguments.length) {
- return this.state.get('checked');
- }
- this.state.set('checked', state);
- return this;
- },
- /**
- * Getter/setter function for the value state.
- *
- * @method value
- * @param {Boolean} [state] State to be set.
- * @return {Boolean|tinymce.ui.Checkbox} True/false or checkbox if it's a set operation.
- */
- value: function(state) {
- if (!arguments.length) {
- return this.checked();
- }
- return this.checked(state);
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix;
- return (
- '<div id="' + id + '" class="' + self.classes + '" unselectable="on" aria-labelledby="' + id + '-al" tabindex="-1">' +
- '<i class="' + prefix + 'ico ' + prefix + 'i-checkbox"></i>' +
- '<span id="' + id + '-al" class="' + prefix + 'label">' + self.encode(self.state.get('text')) + '</span>' +
- '</div>'
- );
- },
- bindStates: function() {
- var self = this;
- function checked(state) {
- self.classes.toggle("checked", state);
- self.aria('checked', state);
- }
- self.state.on('change:text', function(e) {
- self.getEl('al').firstChild.data = self.translate(e.value);
- });
- self.state.on('change:checked change:value', function(e) {
- self.fire('change');
- checked(e.value);
- });
- self.state.on('change:icon', function(e) {
- var icon = e.value, prefix = self.classPrefix;
- if (typeof icon == 'undefined') {
- return self.settings.icon;
- }
- self.settings.icon = icon;
- icon = icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
- var btnElm = self.getEl().firstChild, iconElm = btnElm.getElementsByTagName('i')[0];
- if (icon) {
- if (!iconElm || iconElm != btnElm.firstChild) {
- iconElm = document.createElement('i');
- btnElm.insertBefore(iconElm, btnElm.firstChild);
- }
- iconElm.className = icon;
- } else if (iconElm) {
- btnElm.removeChild(iconElm);
- }
- });
- if (self.state.get('checked')) {
- checked(true);
- }
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ComboBox.js
- /**
- * ComboBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class creates a combobox control. Select box that you select a value from or
- * type a value into.
- *
- * @-x-less ComboBox.less
- * @class tinymce.ui.ComboBox
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/ComboBox", [
- "tinymce/ui/Widget",
- "tinymce/ui/Factory",
- "tinymce/ui/DomUtils",
- "tinymce/dom/DomQuery"
- ], function(Widget, Factory, DomUtils, $) {
- "use strict";
- return Widget.extend({
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} placeholder Placeholder text to display.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- settings = self.settings;
- self.classes.add('combobox');
- self.subinput = true;
- self.ariaTarget = 'inp'; // TODO: Figure out a better way
- settings.menu = settings.menu || settings.values;
- if (settings.menu) {
- settings.icon = 'caret';
- }
- self.on('click', function(e) {
- var elm = e.target, root = self.getEl();
- if (!$.contains(root, elm) && elm != root) {
- return;
- }
- while (elm && elm != root) {
- if (elm.id && elm.id.indexOf('-open') != -1) {
- self.fire('action');
- if (settings.menu) {
- self.showMenu();
- if (e.aria) {
- self.menu.items()[0].focus();
- }
- }
- }
- elm = elm.parentNode;
- }
- });
- // TODO: Rework this
- self.on('keydown', function(e) {
- if (e.target.nodeName == "INPUT" && e.keyCode == 13) {
- self.parents().reverse().each(function(ctrl) {
- var stateValue = self.state.get('value'), inputValue = self.getEl('inp').value;
- e.preventDefault();
- self.state.set('value', inputValue);
- if (stateValue != inputValue) {
- self.fire('change');
- }
- if (ctrl.hasEventListeners('submit') && ctrl.toJSON) {
- ctrl.fire('submit', {data: ctrl.toJSON()});
- return false;
- }
- });
- }
- });
- self.on('keyup', function(e) {
- if (e.target.nodeName == "INPUT") {
- self.state.set('value', e.target.value);
- }
- });
- },
- showMenu: function() {
- var self = this, settings = self.settings, menu;
- if (!self.menu) {
- menu = settings.menu || [];
- // Is menu array then auto constuct menu control
- if (menu.length) {
- menu = {
- type: 'menu',
- items: menu
- };
- } else {
- menu.type = menu.type || 'menu';
- }
- self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
- self.fire('createmenu');
- self.menu.reflow();
- self.menu.on('cancel', function(e) {
- if (e.control === self.menu) {
- self.focus();
- }
- });
- self.menu.on('show hide', function(e) {
- e.control.items().each(function(ctrl) {
- ctrl.active(ctrl.value() == self.value());
- });
- }).fire('show');
- self.menu.on('select', function(e) {
- self.value(e.control.value());
- });
- self.on('focusin', function(e) {
- if (e.target.tagName.toUpperCase() == 'INPUT') {
- self.menu.hide();
- }
- });
- self.aria('expanded', true);
- }
- self.menu.show();
- self.menu.layoutRect({w: self.layoutRect().w});
- self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
- },
- /**
- * Focuses the input area of the control.
- *
- * @method focus
- */
- focus: function() {
- this.getEl('inp').focus();
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, elm = self.getEl(), openElm = self.getEl('open'), rect = self.layoutRect();
- var width, lineHeight;
- if (openElm) {
- width = rect.w - DomUtils.getSize(openElm).width - 10;
- } else {
- width = rect.w - 10;
- }
- // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
- var doc = document;
- if (doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
- lineHeight = (self.layoutRect().h - 2) + 'px';
- }
- $(elm.firstChild).css({
- width: width,
- lineHeight: lineHeight
- });
- self._super();
- return self;
- },
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.ComboBox} Current combobox instance.
- */
- postRender: function() {
- var self = this;
- $(this.getEl('inp')).on('change', function(e) {
- self.state.set('value', e.target.value);
- self.fire('change', e);
- });
- return self._super();
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix;
- var value = self.state.get('value') || '';
- var icon, text, openBtnHtml = '', extraAttrs = '';
- if ("spellcheck" in settings) {
- extraAttrs += ' spellcheck="' + settings.spellcheck + '"';
- }
- if (settings.maxLength) {
- extraAttrs += ' maxlength="' + settings.maxLength + '"';
- }
- if (settings.size) {
- extraAttrs += ' size="' + settings.size + '"';
- }
- if (settings.subtype) {
- extraAttrs += ' type="' + settings.subtype + '"';
- }
- if (self.disabled()) {
- extraAttrs += ' disabled="disabled"';
- }
- icon = settings.icon;
- if (icon && icon != 'caret') {
- icon = prefix + 'ico ' + prefix + 'i-' + settings.icon;
- }
- text = self.state.get('text');
- if (icon || text) {
- openBtnHtml = (
- '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
- '<button id="' + id + '-action" type="button" hidefocus="1" tabindex="-1">' +
- (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
- (text ? (icon ? ' ' : '') + text : '') +
- '</button>' +
- '</div>'
- );
- self.classes.add('has-open');
- }
- return (
- '<div id="' + id + '" class="' + self.classes + '">' +
- '<input id="' + id + '-inp" class="' + prefix + 'textbox" value="' +
- self.encode(value, false) + '" hidefocus="1"' + extraAttrs + ' placeholder="' +
- self.encode(settings.placeholder) + '" />' +
- openBtnHtml +
- '</div>'
- );
- },
- value: function(value) {
- if (arguments.length) {
- this.state.set('value', value);
- return this;
- }
- // Make sure the real state is in sync
- if (this.state.get('rendered')) {
- this.state.set('value', this.getEl('inp').value);
- }
- return this.state.get('value');
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:value', function(e) {
- if (self.getEl('inp').value != e.value) {
- self.getEl('inp').value = e.value;
- }
- });
- self.state.on('change:disabled', function(e) {
- self.getEl('inp').disabled = e.value;
- });
- return self._super();
- },
- remove: function() {
- $(this.getEl('inp')).off();
- this._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ColorBox.js
- /**
- * ColorBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This widget lets you enter colors and browse for colors by pressing the color button. It also displays
- * a preview of the current color.
- *
- * @-x-less ColorBox.less
- * @class tinymce.ui.ColorBox
- * @extends tinymce.ui.ComboBox
- */
- define("tinymce/ui/ColorBox", [
- "tinymce/ui/ComboBox"
- ], function(ComboBox) {
- "use strict";
- return ComboBox.extend({
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- var self = this;
- settings.spellcheck = false;
- if (settings.onaction) {
- settings.icon = 'none';
- }
- self._super(settings);
- self.classes.add('colorbox');
- self.on('change keyup postrender', function() {
- self.repaintColor(self.value());
- });
- },
- repaintColor: function(value) {
- var elm = this.getEl().getElementsByTagName('i')[0];
- if (elm) {
- try {
- elm.style.background = value;
- } catch (ex) {
- // Ignore
- }
- }
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:value', function(e) {
- if (self.state.get('rendered')) {
- self.repaintColor(e.value);
- }
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/PanelButton.js
- /**
- * PanelButton.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new panel button.
- *
- * @class tinymce.ui.PanelButton
- * @extends tinymce.ui.Button
- */
- define("tinymce/ui/PanelButton", [
- "tinymce/ui/Button",
- "tinymce/ui/FloatPanel"
- ], function(Button, FloatPanel) {
- "use strict";
- return Button.extend({
- /**
- * Shows the panel for the button.
- *
- * @method showPanel
- */
- showPanel: function() {
- var self = this, settings = self.settings;
- self.active(true);
- if (!self.panel) {
- var panelSettings = settings.panel;
- // Wrap panel in grid layout if type if specified
- // This makes it possible to add forms or other containers directly in the panel option
- if (panelSettings.type) {
- panelSettings = {
- layout: 'grid',
- items: panelSettings
- };
- }
- panelSettings.role = panelSettings.role || 'dialog';
- panelSettings.popover = true;
- panelSettings.autohide = true;
- panelSettings.ariaRoot = true;
- self.panel = new FloatPanel(panelSettings).on('hide', function() {
- self.active(false);
- }).on('cancel', function(e) {
- e.stopPropagation();
- self.focus();
- self.hidePanel();
- }).parent(self).renderTo(self.getContainerElm());
- self.panel.fire('show');
- self.panel.reflow();
- } else {
- self.panel.show();
- }
- self.panel.moveRel(self.getEl(), settings.popoverAlign || (self.isRtl() ? ['bc-tr', 'bc-tc'] : ['bc-tl', 'bc-tc']));
- },
- /**
- * Hides the panel for the button.
- *
- * @method hidePanel
- */
- hidePanel: function() {
- var self = this;
- if (self.panel) {
- self.panel.hide();
- }
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this;
- self.aria('haspopup', true);
- self.on('click', function(e) {
- if (e.control === self) {
- if (self.panel && self.panel.visible()) {
- self.hidePanel();
- } else {
- self.showPanel();
- self.panel.focus(!!e.aria);
- }
- }
- });
- return self._super();
- },
- remove: function() {
- if (this.panel) {
- this.panel.remove();
- this.panel = null;
- }
- return this._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ColorButton.js
- /**
- * ColorButton.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class creates a color button control. This is a split button in which the main
- * button has a visual representation of the currently selected color. When clicked
- * the caret button displays a color picker, allowing the user to select a new color.
- *
- * @-x-less ColorButton.less
- * @class tinymce.ui.ColorButton
- * @extends tinymce.ui.PanelButton
- */
- define("tinymce/ui/ColorButton", [
- "tinymce/ui/PanelButton",
- "tinymce/dom/DOMUtils"
- ], function(PanelButton, DomUtils) {
- "use strict";
- var DOM = DomUtils.DOM;
- return PanelButton.extend({
- /**
- * Constructs a new ColorButton instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- this._super(settings);
- this.classes.add('colorbutton');
- },
- /**
- * Getter/setter for the current color.
- *
- * @method color
- * @param {String} [color] Color to set.
- * @return {String|tinymce.ui.ColorButton} Current color or current instance.
- */
- color: function(color) {
- if (color) {
- this._color = color;
- this.getEl('preview').style.backgroundColor = color;
- return this;
- }
- return this._color;
- },
- /**
- * Resets the current color.
- *
- * @method resetColor
- * @return {tinymce.ui.ColorButton} Current instance.
- */
- resetColor: function() {
- this._color = null;
- this.getEl('preview').style.backgroundColor = null;
- return this;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix, text = self.state.get('text');
- var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
- var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '',
- textHtml = '';
- if (text) {
- self.classes.add('btn-has-text');
- textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
- }
- return (
- '<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1" aria-haspopup="true">' +
- '<button role="presentation" hidefocus="1" type="button" tabindex="-1">' +
- (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
- '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
- textHtml +
- '</button>' +
- '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
- ' <i class="' + prefix + 'caret"></i>' +
- '</button>' +
- '</div>'
- );
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this, onClickHandler = self.settings.onclick;
- self.on('click', function(e) {
- if (e.aria && e.aria.key == 'down') {
- return;
- }
- if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
- e.stopImmediatePropagation();
- onClickHandler.call(self, e);
- }
- });
- delete self.settings.onclick;
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/util/Color.js
- /**
- * Color.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class lets you parse/serialize colors and convert rgb/hsb.
- *
- * @class tinymce.util.Color
- * @example
- * var white = new tinymce.util.Color({r: 255, g: 255, b: 255});
- * var red = new tinymce.util.Color('#FF0000');
- *
- * console.log(white.toHex(), red.toHsv());
- */
- define("tinymce/util/Color", [], function() {
- var min = Math.min, max = Math.max, round = Math.round;
- /**
- * Constructs a new color instance.
- *
- * @constructor
- * @method Color
- * @param {String} value Optional initial value to parse.
- */
- function Color(value) {
- var self = this, r = 0, g = 0, b = 0;
- function rgb2hsv(r, g, b) {
- var h, s, v, d, minRGB, maxRGB;
- h = 0;
- s = 0;
- v = 0;
- r = r / 255;
- g = g / 255;
- b = b / 255;
- minRGB = min(r, min(g, b));
- maxRGB = max(r, max(g, b));
- if (minRGB == maxRGB) {
- v = minRGB;
- return {
- h: 0,
- s: 0,
- v: v * 100
- };
- }
- /*eslint no-nested-ternary:0 */
- d = (r == minRGB) ? g - b : ((b == minRGB) ? r - g : b - r);
- h = (r == minRGB) ? 3 : ((b == minRGB) ? 1 : 5);
- h = 60 * (h - d / (maxRGB - minRGB));
- s = (maxRGB - minRGB) / maxRGB;
- v = maxRGB;
- return {
- h: round(h),
- s: round(s * 100),
- v: round(v * 100)
- };
- }
- function hsvToRgb(hue, saturation, brightness) {
- var side, chroma, x, match;
- hue = (parseInt(hue, 10) || 0) % 360;
- saturation = parseInt(saturation, 10) / 100;
- brightness = parseInt(brightness, 10) / 100;
- saturation = max(0, min(saturation, 1));
- brightness = max(0, min(brightness, 1));
- if (saturation === 0) {
- r = g = b = round(255 * brightness);
- return;
- }
- side = hue / 60;
- chroma = brightness * saturation;
- x = chroma * (1 - Math.abs(side % 2 - 1));
- match = brightness - chroma;
- switch (Math.floor(side)) {
- case 0:
- r = chroma;
- g = x;
- b = 0;
- break;
- case 1:
- r = x;
- g = chroma;
- b = 0;
- break;
- case 2:
- r = 0;
- g = chroma;
- b = x;
- break;
- case 3:
- r = 0;
- g = x;
- b = chroma;
- break;
- case 4:
- r = x;
- g = 0;
- b = chroma;
- break;
- case 5:
- r = chroma;
- g = 0;
- b = x;
- break;
- default:
- r = g = b = 0;
- }
- r = round(255 * (r + match));
- g = round(255 * (g + match));
- b = round(255 * (b + match));
- }
- /**
- * Returns the hex string of the current color. For example: #ff00ff
- *
- * @method toHex
- * @return {String} Hex string of current color.
- */
- function toHex() {
- function hex(val) {
- val = parseInt(val, 10).toString(16);
- return val.length > 1 ? val : '0' + val;
- }
- return '#' + hex(r) + hex(g) + hex(b);
- }
- /**
- * Returns the r, g, b values of the color. Each channel has a range from 0-255.
- *
- * @method toRgb
- * @return {Object} Object with r, g, b fields.
- */
- function toRgb() {
- return {
- r: r,
- g: g,
- b: b
- };
- }
- /**
- * Returns the h, s, v values of the color. Ranges: h=0-360, s=0-100, v=0-100.
- *
- * @method toHsv
- * @return {Object} Object with h, s, v fields.
- */
- function toHsv() {
- return rgb2hsv(r, g, b);
- }
- /**
- * Parses the specified value and populates the color instance.
- *
- * Supported format examples:
- * * rbg(255,0,0)
- * * #ff0000
- * * #fff
- * * {r: 255, g: 0, b: 0}
- * * {h: 360, s: 100, v: 100}
- *
- * @method parse
- * @param {Object/String} value Color value to parse.
- * @return {tinymce.util.Color} Current color instance.
- */
- function parse(value) {
- var matches;
- if (typeof value == 'object') {
- if ("r" in value) {
- r = value.r;
- g = value.g;
- b = value.b;
- } else if ("v" in value) {
- hsvToRgb(value.h, value.s, value.v);
- }
- } else {
- if ((matches = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)[^\)]*\)/gi.exec(value))) {
- r = parseInt(matches[1], 10);
- g = parseInt(matches[2], 10);
- b = parseInt(matches[3], 10);
- } else if ((matches = /#([0-F]{2})([0-F]{2})([0-F]{2})/gi.exec(value))) {
- r = parseInt(matches[1], 16);
- g = parseInt(matches[2], 16);
- b = parseInt(matches[3], 16);
- } else if ((matches = /#([0-F])([0-F])([0-F])/gi.exec(value))) {
- r = parseInt(matches[1] + matches[1], 16);
- g = parseInt(matches[2] + matches[2], 16);
- b = parseInt(matches[3] + matches[3], 16);
- }
- }
- r = r < 0 ? 0 : (r > 255 ? 255 : r);
- g = g < 0 ? 0 : (g > 255 ? 255 : g);
- b = b < 0 ? 0 : (b > 255 ? 255 : b);
- return self;
- }
- if (value) {
- parse(value);
- }
- self.toRgb = toRgb;
- self.toHsv = toHsv;
- self.toHex = toHex;
- self.parse = parse;
- }
- return Color;
- });
- // Included from: js/tinymce/classes/ui/ColorPicker.js
- /**
- * ColorPicker.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Color picker widget lets you select colors.
- *
- * @-x-less ColorPicker.less
- * @class tinymce.ui.ColorPicker
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/ColorPicker", [
- "tinymce/ui/Widget",
- "tinymce/ui/DragHelper",
- "tinymce/ui/DomUtils",
- "tinymce/util/Color"
- ], function(Widget, DragHelper, DomUtils, Color) {
- "use strict";
- return Widget.extend({
- Defaults: {
- classes: "widget colorpicker"
- },
- /**
- * Constructs a new colorpicker instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} color Initial color value.
- */
- init: function(settings) {
- this._super(settings);
- },
- postRender: function() {
- var self = this, color = self.color(), hsv, hueRootElm, huePointElm, svRootElm, svPointElm;
- hueRootElm = self.getEl('h');
- huePointElm = self.getEl('hp');
- svRootElm = self.getEl('sv');
- svPointElm = self.getEl('svp');
- function getPos(elm, event) {
- var pos = DomUtils.getPos(elm), x, y;
- x = event.pageX - pos.x;
- y = event.pageY - pos.y;
- x = Math.max(0, Math.min(x / elm.clientWidth, 1));
- y = Math.max(0, Math.min(y / elm.clientHeight, 1));
- return {
- x: x,
- y: y
- };
- }
- function updateColor(hsv, hueUpdate) {
- var hue = (360 - hsv.h) / 360;
- DomUtils.css(huePointElm, {
- top: (hue * 100) + '%'
- });
- if (!hueUpdate) {
- DomUtils.css(svPointElm, {
- left: hsv.s + '%',
- top: (100 - hsv.v) + '%'
- });
- }
- svRootElm.style.background = new Color({s: 100, v: 100, h: hsv.h}).toHex();
- self.color().parse({s: hsv.s, v: hsv.v, h: hsv.h});
- }
- function updateSaturationAndValue(e) {
- var pos;
- pos = getPos(svRootElm, e);
- hsv.s = pos.x * 100;
- hsv.v = (1 - pos.y) * 100;
- updateColor(hsv);
- self.fire('change');
- }
- function updateHue(e) {
- var pos;
- pos = getPos(hueRootElm, e);
- hsv = color.toHsv();
- hsv.h = (1 - pos.y) * 360;
- updateColor(hsv, true);
- self.fire('change');
- }
- self._repaint = function() {
- hsv = color.toHsv();
- updateColor(hsv);
- };
- self._super();
- self._svdraghelper = new DragHelper(self._id + '-sv', {
- start: updateSaturationAndValue,
- drag: updateSaturationAndValue
- });
- self._hdraghelper = new DragHelper(self._id + '-h', {
- start: updateHue,
- drag: updateHue
- });
- self._repaint();
- },
- rgb: function() {
- return this.color().toRgb();
- },
- value: function(value) {
- var self = this;
- if (arguments.length) {
- self.color().parse(value);
- if (self._rendered) {
- self._repaint();
- }
- } else {
- return self.color().toHex();
- }
- },
- color: function() {
- if (!this._color) {
- this._color = new Color();
- }
- return this._color;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix, hueHtml;
- var stops = '#ff0000,#ff0080,#ff00ff,#8000ff,#0000ff,#0080ff,#00ffff,#00ff80,#00ff00,#80ff00,#ffff00,#ff8000,#ff0000';
- function getOldIeFallbackHtml() {
- var i, l, html = '', gradientPrefix, stopsList;
- gradientPrefix = 'filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr=';
- stopsList = stops.split(',');
- for (i = 0, l = stopsList.length - 1; i < l; i++) {
- html += (
- '<div class="' + prefix + 'colorpicker-h-chunk" style="' +
- 'height:' + (100 / l) + '%;' +
- gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ');' +
- '-ms-' + gradientPrefix + stopsList[i] + ',endColorstr=' + stopsList[i + 1] + ')' +
- '"></div>'
- );
- }
- return html;
- }
- var gradientCssText = (
- 'background: -ms-linear-gradient(top,' + stops + ');' +
- 'background: linear-gradient(to bottom,' + stops + ');'
- );
- hueHtml = (
- '<div id="' + id + '-h" class="' + prefix + 'colorpicker-h" style="' + gradientCssText + '">' +
- getOldIeFallbackHtml() +
- '<div id="' + id + '-hp" class="' + prefix + 'colorpicker-h-marker"></div>' +
- '</div>'
- );
- return (
- '<div id="' + id + '" class="' + self.classes + '">' +
- '<div id="' + id + '-sv" class="' + prefix + 'colorpicker-sv">' +
- '<div class="' + prefix + 'colorpicker-overlay1">' +
- '<div class="' + prefix + 'colorpicker-overlay2">' +
- '<div id="' + id + '-svp" class="' + prefix + 'colorpicker-selector1">' +
- '<div class="' + prefix + 'colorpicker-selector2"></div>' +
- '</div>' +
- '</div>' +
- '</div>' +
- '</div>' +
- hueHtml +
- '</div>'
- );
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Path.js
- /**
- * Path.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new path control.
- *
- * @-x-less Path.less
- * @class tinymce.ui.Path
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Path", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- return Widget.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {String} delimiter Delimiter to display between row in path.
- */
- init: function(settings) {
- var self = this;
- if (!settings.delimiter) {
- settings.delimiter = '\u00BB';
- }
- self._super(settings);
- self.classes.add('path');
- self.canFocus = true;
- self.on('click', function(e) {
- var index, target = e.target;
- if ((index = target.getAttribute('data-index'))) {
- self.fire('select', {value: self.row()[index], index: index});
- }
- });
- self.row(self.settings.row);
- },
- /**
- * Focuses the current control.
- *
- * @method focus
- * @return {tinymce.ui.Control} Current control instance.
- */
- focus: function() {
- var self = this;
- self.getEl().firstChild.focus();
- return self;
- },
- /**
- * Sets/gets the data to be used for the path.
- *
- * @method row
- * @param {Array} row Array with row name is rendered to path.
- */
- row: function(row) {
- if (!arguments.length) {
- return this.state.get('row');
- }
- this.state.set('row', row);
- return this;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this;
- return (
- '<div id="' + self._id + '" class="' + self.classes + '">' +
- self._getDataPathHtml(self.state.get('row')) +
- '</div>'
- );
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:row', function(e) {
- self.innerHtml(self._getDataPathHtml(e.value));
- });
- return self._super();
- },
- _getDataPathHtml: function(data) {
- var self = this, parts = data || [], i, l, html = '', prefix = self.classPrefix;
- for (i = 0, l = parts.length; i < l; i++) {
- html += (
- (i > 0 ? '<div class="' + prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
- '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
- i + '" tabindex="-1" id="' + self._id + '-' + i + '" aria-level="' + i + '">' + parts[i].name + '</div>'
- );
- }
- if (!html) {
- html = '<div class="' + prefix + 'path-item">\u00a0</div>';
- }
- return html;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ElementPath.js
- /**
- * ElementPath.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This control creates an path for the current selections parent elements in TinyMCE.
- *
- * @class tinymce.ui.ElementPath
- * @extends tinymce.ui.Path
- */
- define("tinymce/ui/ElementPath", [
- "tinymce/ui/Path"
- ], function(Path) {
- return Path.extend({
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.ElementPath} Current combobox instance.
- */
- postRender: function() {
- var self = this, editor = self.settings.editor;
- function isHidden(elm) {
- if (elm.nodeType === 1) {
- if (elm.nodeName == "BR" || !!elm.getAttribute('data-mce-bogus')) {
- return true;
- }
- if (elm.getAttribute('data-mce-type') === 'bookmark') {
- return true;
- }
- }
- return false;
- }
- if (editor.settings.elementpath !== false) {
- self.on('select', function(e) {
- editor.focus();
- editor.selection.select(this.row()[e.index].element);
- editor.nodeChanged();
- });
- editor.on('nodeChange', function(e) {
- var outParents = [], parents = e.parents, i = parents.length;
- while (i--) {
- if (parents[i].nodeType == 1 && !isHidden(parents[i])) {
- var args = editor.fire('ResolveName', {
- name: parents[i].nodeName.toLowerCase(),
- target: parents[i]
- });
- if (!args.isDefaultPrevented()) {
- outParents.push({name: args.name, element: parents[i]});
- }
- if (args.isPropagationStopped()) {
- break;
- }
- }
- }
- self.row(outParents);
- });
- }
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FormItem.js
- /**
- * FormItem.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class is a container created by the form element with
- * a label and control item.
- *
- * @class tinymce.ui.FormItem
- * @extends tinymce.ui.Container
- * @setting {String} label Label to display for the form item.
- */
- define("tinymce/ui/FormItem", [
- "tinymce/ui/Container"
- ], function(Container) {
- "use strict";
- return Container.extend({
- Defaults: {
- layout: 'flex',
- align: 'center',
- defaults: {
- flex: 1
- }
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout, prefix = self.classPrefix;
- self.classes.add('formitem');
- layout.preRender(self);
- return (
- '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' +
- (self.settings.title ? ('<div id="' + self._id + '-title" class="' + prefix + 'title">' +
- self.settings.title + '</div>') : '') +
- '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
- (self.settings.html || '') + layout.renderHtml(self) +
- '</div>' +
- '</div>'
- );
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Form.js
- /**
- * Form.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class creates a form container. A form container has the ability
- * to automatically wrap items in tinymce.ui.FormItem instances.
- *
- * Each FormItem instance is a container for the label and the item.
- *
- * @example
- * tinymce.ui.Factory.create({
- * type: 'form',
- * items: [
- * {type: 'textbox', label: 'My text box'}
- * ]
- * }).renderTo(document.body);
- *
- * @class tinymce.ui.Form
- * @extends tinymce.ui.Container
- */
- define("tinymce/ui/Form", [
- "tinymce/ui/Container",
- "tinymce/ui/FormItem",
- "tinymce/util/Tools"
- ], function(Container, FormItem, Tools) {
- "use strict";
- return Container.extend({
- Defaults: {
- containerCls: 'form',
- layout: 'flex',
- direction: 'column',
- align: 'stretch',
- flex: 1,
- padding: 20,
- labelGap: 30,
- spacing: 10,
- callbacks: {
- submit: function() {
- this.submit();
- }
- }
- },
- /**
- * This method gets invoked before the control is rendered.
- *
- * @method preRender
- */
- preRender: function() {
- var self = this, items = self.items();
- if (!self.settings.formItemDefaults) {
- self.settings.formItemDefaults = {
- layout: 'flex',
- autoResize: "overflow",
- defaults: {flex: 1}
- };
- }
- // Wrap any labeled items in FormItems
- items.each(function(ctrl) {
- var formItem, label = ctrl.settings.label;
- if (label) {
- formItem = new FormItem(Tools.extend({
- items: {
- type: 'label',
- id: ctrl._id + '-l',
- text: label,
- flex: 0,
- forId: ctrl._id,
- disabled: ctrl.disabled()
- }
- }, self.settings.formItemDefaults));
- formItem.type = 'formitem';
- ctrl.aria('labelledby', ctrl._id + '-l');
- if (typeof ctrl.settings.flex == "undefined") {
- ctrl.settings.flex = 1;
- }
- self.replace(ctrl, formItem);
- formItem.add(ctrl);
- }
- });
- },
- /**
- * Fires a submit event with the serialized form.
- *
- * @method submit
- * @return {Object} Event arguments object.
- */
- submit: function() {
- return this.fire('submit', {data: this.toJSON()});
- },
- /**
- * Post render method. Called after the control has been rendered to the target.
- *
- * @method postRender
- * @return {tinymce.ui.ComboBox} Current combobox instance.
- */
- postRender: function() {
- var self = this;
- self._super();
- self.fromJSON(self.settings.data);
- },
- bindStates: function() {
- var self = this;
- self._super();
- function recalcLabels() {
- var maxLabelWidth = 0, labels = [], i, labelGap, items;
- if (self.settings.labelGapCalc === false) {
- return;
- }
- if (self.settings.labelGapCalc == "children") {
- items = self.find('formitem');
- } else {
- items = self.items();
- }
- items.filter('formitem').each(function(item) {
- var labelCtrl = item.items()[0], labelWidth = labelCtrl.getEl().clientWidth;
- maxLabelWidth = labelWidth > maxLabelWidth ? labelWidth : maxLabelWidth;
- labels.push(labelCtrl);
- });
- labelGap = self.settings.labelGap || 0;
- i = labels.length;
- while (i--) {
- labels[i].settings.minWidth = maxLabelWidth + labelGap;
- }
- }
- self.on('show', recalcLabels);
- recalcLabels();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FieldSet.js
- /**
- * FieldSet.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class creates fieldset containers.
- *
- * @-x-less FieldSet.less
- * @class tinymce.ui.FieldSet
- * @extends tinymce.ui.Form
- */
- define("tinymce/ui/FieldSet", [
- "tinymce/ui/Form"
- ], function(Form) {
- "use strict";
- return Form.extend({
- Defaults: {
- containerCls: 'fieldset',
- layout: 'flex',
- direction: 'column',
- align: 'stretch',
- flex: 1,
- padding: "25 15 5 15",
- labelGap: 30,
- spacing: 10,
- border: 1
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout, prefix = self.classPrefix;
- self.preRender();
- layout.preRender(self);
- return (
- '<fieldset id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' +
- (self.settings.title ? ('<legend id="' + self._id + '-title" class="' + prefix + 'fieldset-title">' +
- self.settings.title + '</legend>') : '') +
- '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
- (self.settings.html || '') + layout.renderHtml(self) +
- '</div>' +
- '</fieldset>'
- );
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FilePicker.js
- /**
- * FilePicker.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*global tinymce:true */
- /**
- * This class creates a file picker control.
- *
- * @class tinymce.ui.FilePicker
- * @extends tinymce.ui.ComboBox
- */
- define("tinymce/ui/FilePicker", [
- "tinymce/ui/ComboBox",
- "tinymce/util/Tools"
- ], function(ComboBox, Tools) {
- "use strict";
- return ComboBox.extend({
- /**
- * Constructs a new control instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- var self = this, editor = tinymce.activeEditor, editorSettings = editor.settings;
- var actionCallback, fileBrowserCallback, fileBrowserCallbackTypes;
- settings.spellcheck = false;
- fileBrowserCallbackTypes = editorSettings.file_picker_types || editorSettings.file_browser_callback_types;
- if (fileBrowserCallbackTypes) {
- fileBrowserCallbackTypes = Tools.makeMap(fileBrowserCallbackTypes, /[, ]/);
- }
- if (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype]) {
- fileBrowserCallback = editorSettings.file_picker_callback;
- if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
- actionCallback = function() {
- var meta = self.fire('beforecall').meta;
- meta = Tools.extend({filetype: settings.filetype}, meta);
- // file_picker_callback(callback, currentValue, metaData)
- fileBrowserCallback.call(
- editor,
- function(value, meta) {
- self.value(value).fire('change', {meta: meta});
- },
- self.value(),
- meta
- );
- };
- } else {
- // Legacy callback: file_picker_callback(id, currentValue, filetype, window)
- fileBrowserCallback = editorSettings.file_browser_callback;
- if (fileBrowserCallback && (!fileBrowserCallbackTypes || fileBrowserCallbackTypes[settings.filetype])) {
- actionCallback = function() {
- fileBrowserCallback(
- self.getEl('inp').id,
- self.value(),
- settings.filetype,
- window
- );
- };
- }
- }
- }
- if (actionCallback) {
- settings.icon = 'browse';
- settings.onaction = actionCallback;
- }
- self._super(settings);
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FitLayout.js
- /**
- * FitLayout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This layout manager will resize the control to be the size of it's parent container.
- * In other words width: 100% and height: 100%.
- *
- * @-x-less FitLayout.less
- * @class tinymce.ui.FitLayout
- * @extends tinymce.ui.AbsoluteLayout
- */
- define("tinymce/ui/FitLayout", [
- "tinymce/ui/AbsoluteLayout"
- ], function(AbsoluteLayout) {
- "use strict";
- return AbsoluteLayout.extend({
- /**
- * Recalculates the positions of the controls in the specified container.
- *
- * @method recalc
- * @param {tinymce.ui.Container} container Container instance to recalc.
- */
- recalc: function(container) {
- var contLayoutRect = container.layoutRect(), paddingBox = container.paddingBox;
- container.items().filter(':visible').each(function(ctrl) {
- ctrl.layoutRect({
- x: paddingBox.left,
- y: paddingBox.top,
- w: contLayoutRect.innerW - paddingBox.right - paddingBox.left,
- h: contLayoutRect.innerH - paddingBox.top - paddingBox.bottom
- });
- if (ctrl.recalc) {
- ctrl.recalc();
- }
- });
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FlexLayout.js
- /**
- * FlexLayout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This layout manager works similar to the CSS flex box.
- *
- * @setting {String} direction row|row-reverse|column|column-reverse
- * @setting {Number} flex A positive-number to flex by.
- * @setting {String} align start|end|center|stretch
- * @setting {String} pack start|end|justify
- *
- * @class tinymce.ui.FlexLayout
- * @extends tinymce.ui.AbsoluteLayout
- */
- define("tinymce/ui/FlexLayout", [
- "tinymce/ui/AbsoluteLayout"
- ], function(AbsoluteLayout) {
- "use strict";
- return AbsoluteLayout.extend({
- /**
- * Recalculates the positions of the controls in the specified container.
- *
- * @method recalc
- * @param {tinymce.ui.Container} container Container instance to recalc.
- */
- recalc: function(container) {
- // A ton of variables, needs to be in the same scope for performance
- var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
- var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
- var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
- var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
- var alignDeltaSizeName, alignContentSizeName;
- var max = Math.max, min = Math.min;
- // Get container items, properties and settings
- items = container.items().filter(':visible');
- contLayoutRect = container.layoutRect();
- contPaddingBox = container.paddingBox;
- contSettings = container.settings;
- direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
- align = contSettings.align;
- pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
- spacing = contSettings.spacing || 0;
- if (direction == "row-reversed" || direction == "column-reverse") {
- items = items.set(items.toArray().reverse());
- direction = direction.split('-')[0];
- }
- // Setup axis variable name for row/column direction since the calculations is the same
- if (direction == "column") {
- posName = "y";
- sizeName = "h";
- minSizeName = "minH";
- maxSizeName = "maxH";
- innerSizeName = "innerH";
- beforeName = 'top';
- deltaSizeName = "deltaH";
- contentSizeName = "contentH";
- alignBeforeName = "left";
- alignSizeName = "w";
- alignAxisName = "x";
- alignInnerSizeName = "innerW";
- alignMinSizeName = "minW";
- alignAfterName = "right";
- alignDeltaSizeName = "deltaW";
- alignContentSizeName = "contentW";
- } else {
- posName = "x";
- sizeName = "w";
- minSizeName = "minW";
- maxSizeName = "maxW";
- innerSizeName = "innerW";
- beforeName = 'left';
- deltaSizeName = "deltaW";
- contentSizeName = "contentW";
- alignBeforeName = "top";
- alignSizeName = "h";
- alignAxisName = "y";
- alignInnerSizeName = "innerH";
- alignMinSizeName = "minH";
- alignAfterName = "bottom";
- alignDeltaSizeName = "deltaH";
- alignContentSizeName = "contentH";
- }
- // Figure out total flex, availableSpace and collect any max size elements
- availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
- maxAlignEndPos = totalFlex = 0;
- for (i = 0, l = items.length; i < l; i++) {
- ctrl = items[i];
- ctrlLayoutRect = ctrl.layoutRect();
- ctrlSettings = ctrl.settings;
- flex = ctrlSettings.flex;
- availableSpace -= (i < l - 1 ? spacing : 0);
- if (flex > 0) {
- totalFlex += flex;
- // Flexed item has a max size then we need to check if we will hit that size
- if (ctrlLayoutRect[maxSizeName]) {
- maxSizeItems.push(ctrl);
- }
- ctrlLayoutRect.flex = flex;
- }
- availableSpace -= ctrlLayoutRect[minSizeName];
- // Calculate the align end position to be used to check for overflow/underflow
- size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
- if (size > maxAlignEndPos) {
- maxAlignEndPos = size;
- }
- }
- // Calculate minW/minH
- rect = {};
- if (availableSpace < 0) {
- rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
- } else {
- rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
- }
- rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
- rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
- rect[alignContentSizeName] = maxAlignEndPos;
- rect.minW = min(rect.minW, contLayoutRect.maxW);
- rect.minH = min(rect.minH, contLayoutRect.maxH);
- rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
- rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
- // Resize container container if minSize was changed
- if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
- rect.w = rect.minW;
- rect.h = rect.minH;
- container.layoutRect(rect);
- this.recalc(container);
- // Forced recalc for example if items are hidden/shown
- if (container._lastRect === null) {
- var parentCtrl = container.parent();
- if (parentCtrl) {
- parentCtrl._lastRect = null;
- parentCtrl.recalc();
- }
- }
- return;
- }
- // Handle max size elements, check if they will become to wide with current options
- ratio = availableSpace / totalFlex;
- for (i = 0, l = maxSizeItems.length; i < l; i++) {
- ctrl = maxSizeItems[i];
- ctrlLayoutRect = ctrl.layoutRect();
- maxSize = ctrlLayoutRect[maxSizeName];
- size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
- if (size > maxSize) {
- availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
- totalFlex -= ctrlLayoutRect.flex;
- ctrlLayoutRect.flex = 0;
- ctrlLayoutRect.maxFlexSize = maxSize;
- } else {
- ctrlLayoutRect.maxFlexSize = 0;
- }
- }
- // Setup new ratio, target layout rect, start position
- ratio = availableSpace / totalFlex;
- pos = contPaddingBox[beforeName];
- rect = {};
- // Handle pack setting moves the start position to end, center
- if (totalFlex === 0) {
- if (pack == "end") {
- pos = availableSpace + contPaddingBox[beforeName];
- } else if (pack == "center") {
- pos = Math.round(
- (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
- ) + contPaddingBox[beforeName];
- if (pos < 0) {
- pos = contPaddingBox[beforeName];
- }
- } else if (pack == "justify") {
- pos = contPaddingBox[beforeName];
- spacing = Math.floor(availableSpace / (items.length - 1));
- }
- }
- // Default aligning (start) the other ones needs to be calculated while doing the layout
- rect[alignAxisName] = contPaddingBox[alignBeforeName];
- // Start laying out controls
- for (i = 0, l = items.length; i < l; i++) {
- ctrl = items[i];
- ctrlLayoutRect = ctrl.layoutRect();
- size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
- // Align the control on the other axis
- if (align === "center") {
- rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
- } else if (align === "stretch") {
- rect[alignSizeName] = max(
- ctrlLayoutRect[alignMinSizeName] || 0,
- contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
- );
- rect[alignAxisName] = contPaddingBox[alignBeforeName];
- } else if (align === "end") {
- rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
- }
- // Calculate new size based on flex
- if (ctrlLayoutRect.flex > 0) {
- size += ctrlLayoutRect.flex * ratio;
- }
- rect[sizeName] = size;
- rect[posName] = pos;
- ctrl.layoutRect(rect);
- // Recalculate containers
- if (ctrl.recalc) {
- ctrl.recalc();
- }
- // Move x/y position
- pos += size + spacing;
- }
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FlowLayout.js
- /**
- * FlowLayout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This layout manager will place the controls by using the browsers native layout.
- *
- * @-x-less FlowLayout.less
- * @class tinymce.ui.FlowLayout
- * @extends tinymce.ui.Layout
- */
- define("tinymce/ui/FlowLayout", [
- "tinymce/ui/Layout"
- ], function(Layout) {
- return Layout.extend({
- Defaults: {
- containerClass: 'flow-layout',
- controlClass: 'flow-layout-item',
- endClass: 'break'
- },
- /**
- * Recalculates the positions of the controls in the specified container.
- *
- * @method recalc
- * @param {tinymce.ui.Container} container Container instance to recalc.
- */
- recalc: function(container) {
- container.items().filter(':visible').each(function(ctrl) {
- if (ctrl.recalc) {
- ctrl.recalc();
- }
- });
- },
- isNative: function() {
- return true;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/FormatControls.js
- /**
- * FormatControls.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Internal class containing all TinyMCE specific control types such as
- * format listboxes, fontlist boxes, toolbar buttons etc.
- *
- * @class tinymce.ui.FormatControls
- */
- define("tinymce/ui/FormatControls", [
- "tinymce/ui/Control",
- "tinymce/ui/Widget",
- "tinymce/ui/FloatPanel",
- "tinymce/util/Tools",
- "tinymce/EditorManager",
- "tinymce/Env"
- ], function(Control, Widget, FloatPanel, Tools, EditorManager, Env) {
- var each = Tools.each;
- EditorManager.on('AddEditor', function(e) {
- if (e.editor.rtl) {
- Control.rtl = true;
- }
- registerControls(e.editor);
- });
- Control.translate = function(text) {
- return EditorManager.translate(text);
- };
- Widget.tooltips = !Env.iOS;
- function registerControls(editor) {
- var formatMenu;
- function createListBoxChangeHandler(items, formatName) {
- return function() {
- var self = this;
- editor.on('nodeChange', function(e) {
- var formatter = editor.formatter;
- var value = null;
- each(e.parents, function(node) {
- each(items, function(item) {
- if (formatName) {
- if (formatter.matchNode(node, formatName, {value: item.value})) {
- value = item.value;
- }
- } else {
- if (formatter.matchNode(node, item.value)) {
- value = item.value;
- }
- }
- if (value) {
- return false;
- }
- });
- if (value) {
- return false;
- }
- });
- self.value(value);
- });
- };
- }
- function createFormats(formats) {
- formats = formats.replace(/;$/, '').split(';');
- var i = formats.length;
- while (i--) {
- formats[i] = formats[i].split('=');
- }
- return formats;
- }
- function createFormatMenu() {
- var count = 0, newFormats = [];
- var defaultStyleFormats = [
- {title: 'Headings', items: [
- {title: 'Heading 1', format: 'h1'},
- {title: 'Heading 2', format: 'h2'},
- {title: 'Heading 3', format: 'h3'},
- {title: 'Heading 4', format: 'h4'},
- {title: 'Heading 5', format: 'h5'},
- {title: 'Heading 6', format: 'h6'}
- ]},
- {title: 'Inline', items: [
- {title: 'Bold', icon: 'bold', format: 'bold'},
- {title: 'Italic', icon: 'italic', format: 'italic'},
- {title: 'Underline', icon: 'underline', format: 'underline'},
- {title: 'Strikethrough', icon: 'strikethrough', format: 'strikethrough'},
- {title: 'Superscript', icon: 'superscript', format: 'superscript'},
- {title: 'Subscript', icon: 'subscript', format: 'subscript'},
- {title: 'Code', icon: 'code', format: 'code'}
- ]},
- {title: 'Blocks', items: [
- {title: 'Paragraph', format: 'p'},
- {title: 'Blockquote', format: 'blockquote'},
- {title: 'Div', format: 'div'},
- {title: 'Pre', format: 'pre'}
- ]},
- {title: 'Alignment', items: [
- {title: 'Left', icon: 'alignleft', format: 'alignleft'},
- {title: 'Center', icon: 'aligncenter', format: 'aligncenter'},
- {title: 'Right', icon: 'alignright', format: 'alignright'},
- {title: 'Justify', icon: 'alignjustify', format: 'alignjustify'}
- ]}
- ];
- function createMenu(formats) {
- var menu = [];
- if (!formats) {
- return;
- }
- each(formats, function(format) {
- var menuItem = {
- text: format.title,
- icon: format.icon
- };
- if (format.items) {
- menuItem.menu = createMenu(format.items);
- } else {
- var formatName = format.format || "custom" + count++;
- if (!format.format) {
- format.name = formatName;
- newFormats.push(format);
- }
- menuItem.format = formatName;
- menuItem.cmd = format.cmd;
- }
- menu.push(menuItem);
- });
- return menu;
- }
- function createStylesMenu() {
- var menu;
- if (editor.settings.style_formats_merge) {
- if (editor.settings.style_formats) {
- menu = createMenu(defaultStyleFormats.concat(editor.settings.style_formats));
- } else {
- menu = createMenu(defaultStyleFormats);
- }
- } else {
- menu = createMenu(editor.settings.style_formats || defaultStyleFormats);
- }
- return menu;
- }
- editor.on('init', function() {
- each(newFormats, function(format) {
- editor.formatter.register(format.name, format);
- });
- });
- return {
- type: 'menu',
- items: createStylesMenu(),
- onPostRender: function(e) {
- editor.fire('renderFormatsMenu', {control: e.control});
- },
- itemDefaults: {
- preview: true,
- textStyle: function() {
- if (this.settings.format) {
- return editor.formatter.getCssText(this.settings.format);
- }
- },
- onPostRender: function() {
- var self = this;
- self.parent().on('show', function() {
- var formatName, command;
- formatName = self.settings.format;
- if (formatName) {
- self.disabled(!editor.formatter.canApply(formatName));
- self.active(editor.formatter.match(formatName));
- }
- command = self.settings.cmd;
- if (command) {
- self.active(editor.queryCommandState(command));
- }
- });
- },
- onclick: function() {
- if (this.settings.format) {
- toggleFormat(this.settings.format);
- }
- if (this.settings.cmd) {
- editor.execCommand(this.settings.cmd);
- }
- }
- }
- };
- }
- formatMenu = createFormatMenu();
- function initOnPostRender(name) {
- return function() {
- var self = this;
- // TODO: Fix this
- if (editor.formatter) {
- editor.formatter.formatChanged(name, function(state) {
- self.active(state);
- });
- } else {
- editor.on('init', function() {
- editor.formatter.formatChanged(name, function(state) {
- self.active(state);
- });
- });
- }
- };
- }
- // Simple format controls <control/format>:<UI text>
- each({
- bold: 'Bold',
- italic: 'Italic',
- underline: 'Underline',
- strikethrough: 'Strikethrough',
- subscript: 'Subscript',
- superscript: 'Superscript'
- }, function(text, name) {
- editor.addButton(name, {
- tooltip: text,
- onPostRender: initOnPostRender(name),
- onclick: function() {
- toggleFormat(name);
- }
- });
- });
- // Simple command controls <control>:[<UI text>,<Command>]
- each({
- outdent: ['Decrease indent', 'Outdent'],
- indent: ['Increase indent', 'Indent'],
- cut: ['Cut', 'Cut'],
- copy: ['Copy', 'Copy'],
- paste: ['Paste', 'Paste'],
- help: ['Help', 'mceHelp'],
- selectall: ['Select all', 'SelectAll'],
- removeformat: ['Clear formatting', 'RemoveFormat'],
- visualaid: ['Visual aids', 'mceToggleVisualAid'],
- newdocument: ['New document', 'mceNewDocument']
- }, function(item, name) {
- editor.addButton(name, {
- tooltip: item[0],
- cmd: item[1]
- });
- });
- // Simple command controls with format state
- each({
- blockquote: ['Blockquote', 'mceBlockQuote'],
- numlist: ['Numbered list', 'InsertOrderedList'],
- bullist: ['Bullet list', 'InsertUnorderedList'],
- subscript: ['Subscript', 'Subscript'],
- superscript: ['Superscript', 'Superscript'],
- alignleft: ['Align left', 'JustifyLeft'],
- aligncenter: ['Align center', 'JustifyCenter'],
- alignright: ['Align right', 'JustifyRight'],
- alignjustify: ['Justify', 'JustifyFull'],
- alignnone: ['No alignment', 'JustifyNone']
- }, function(item, name) {
- editor.addButton(name, {
- tooltip: item[0],
- cmd: item[1],
- onPostRender: initOnPostRender(name)
- });
- });
- function toggleUndoRedoState(type) {
- return function() {
- var self = this;
- type = type == 'redo' ? 'hasRedo' : 'hasUndo';
- function checkState() {
- return editor.undoManager ? editor.undoManager[type]() : false;
- }
- self.disabled(!checkState());
- editor.on('Undo Redo AddUndo TypingUndo ClearUndos SwitchMode', function() {
- self.disabled(editor.readonly || !checkState());
- });
- };
- }
- function toggleVisualAidState() {
- var self = this;
- editor.on('VisualAid', function(e) {
- self.active(e.hasVisual);
- });
- self.active(editor.hasVisual);
- }
- editor.addButton('undo', {
- tooltip: 'Undo',
- onPostRender: toggleUndoRedoState('undo'),
- cmd: 'undo'
- });
- editor.addButton('redo', {
- tooltip: 'Redo',
- onPostRender: toggleUndoRedoState('redo'),
- cmd: 'redo'
- });
- editor.addMenuItem('newdocument', {
- text: 'New document',
- icon: 'newdocument',
- cmd: 'mceNewDocument'
- });
- editor.addMenuItem('undo', {
- text: 'Undo',
- icon: 'undo',
- shortcut: 'Meta+Z',
- onPostRender: toggleUndoRedoState('undo'),
- cmd: 'undo'
- });
- editor.addMenuItem('redo', {
- text: 'Redo',
- icon: 'redo',
- shortcut: 'Meta+Y',
- onPostRender: toggleUndoRedoState('redo'),
- cmd: 'redo'
- });
- editor.addMenuItem('visualaid', {
- text: 'Visual aids',
- selectable: true,
- onPostRender: toggleVisualAidState,
- cmd: 'mceToggleVisualAid'
- });
- editor.addButton('remove', {
- tooltip: 'Remove',
- icon: 'remove',
- cmd: 'Delete'
- });
- each({
- cut: ['Cut', 'Cut', 'Meta+X'],
- copy: ['Copy', 'Copy', 'Meta+C'],
- paste: ['Paste', 'Paste', 'Meta+V'],
- selectall: ['Select all', 'SelectAll', 'Meta+A'],
- bold: ['Bold', 'Bold', 'Meta+B'],
- italic: ['Italic', 'Italic', 'Meta+I'],
- underline: ['Underline', 'Underline'],
- strikethrough: ['Strikethrough', 'Strikethrough'],
- subscript: ['Subscript', 'Subscript'],
- superscript: ['Superscript', 'Superscript'],
- removeformat: ['Clear formatting', 'RemoveFormat']
- }, function(item, name) {
- editor.addMenuItem(name, {
- text: item[0],
- icon: name,
- shortcut: item[2],
- cmd: item[1]
- });
- });
- editor.on('mousedown', function() {
- FloatPanel.hideAll();
- });
- function toggleFormat(fmt) {
- if (fmt.control) {
- fmt = fmt.control.value();
- }
- if (fmt) {
- editor.execCommand('mceToggleFormat', false, fmt);
- }
- }
- editor.addButton('styleselect', {
- type: 'menubutton',
- text: 'Formats',
- menu: formatMenu
- });
- editor.addButton('formatselect', function() {
- var items = [], blocks = createFormats(editor.settings.block_formats ||
- 'Paragraph=p;' +
- 'Heading 1=h1;' +
- 'Heading 2=h2;' +
- 'Heading 3=h3;' +
- 'Heading 4=h4;' +
- 'Heading 5=h5;' +
- 'Heading 6=h6;' +
- 'Preformatted=pre'
- );
- each(blocks, function(block) {
- items.push({
- text: block[0],
- value: block[1],
- textStyle: function() {
- return editor.formatter.getCssText(block[1]);
- }
- });
- });
- return {
- type: 'listbox',
- text: blocks[0][0],
- values: items,
- fixedWidth: true,
- onselect: toggleFormat,
- onPostRender: createListBoxChangeHandler(items)
- };
- });
- editor.addButton('fontselect', function() {
- var defaultFontsFormats =
- 'Andale Mono=andale mono,monospace;' +
- 'Arial=arial,helvetica,sans-serif;' +
- 'Arial Black=arial black,sans-serif;' +
- 'Book Antiqua=book antiqua,palatino,serif;' +
- 'Comic Sans MS=comic sans ms,sans-serif;' +
- 'Courier New=courier new,courier,monospace;' +
- 'Georgia=georgia,palatino,serif;' +
- 'Helvetica=helvetica,arial,sans-serif;' +
- 'Impact=impact,sans-serif;' +
- 'Symbol=symbol;' +
- 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
- 'Terminal=terminal,monaco,monospace;' +
- 'Times New Roman=times new roman,times,serif;' +
- 'Trebuchet MS=trebuchet ms,geneva,sans-serif;' +
- 'Verdana=verdana,geneva,sans-serif;' +
- 'Webdings=webdings;' +
- 'Wingdings=wingdings,zapf dingbats';
- var items = [], fonts = createFormats(editor.settings.font_formats || defaultFontsFormats);
- each(fonts, function(font) {
- items.push({
- text: {raw: font[0]},
- value: font[1],
- textStyle: font[1].indexOf('dings') == -1 ? 'font-family:' + font[1] : ''
- });
- });
- return {
- type: 'listbox',
- text: 'Font Family',
- tooltip: 'Font Family',
- values: items,
- fixedWidth: true,
- onPostRender: createListBoxChangeHandler(items, 'fontname'),
- onselect: function(e) {
- if (e.control.settings.value) {
- editor.execCommand('FontName', false, e.control.settings.value);
- }
- }
- };
- });
- editor.addButton('fontsizeselect', function() {
- var items = [], defaultFontsizeFormats = '8pt 10pt 12pt 14pt 18pt 24pt 36pt';
- var fontsize_formats = editor.settings.fontsize_formats || defaultFontsizeFormats;
- each(fontsize_formats.split(' '), function(item) {
- var text = item, value = item;
- // Allow text=value font sizes.
- var values = item.split('=');
- if (values.length > 1) {
- text = values[0];
- value = values[1];
- }
- items.push({text: text, value: value});
- });
- return {
- type: 'listbox',
- text: 'Font Sizes',
- tooltip: 'Font Sizes',
- values: items,
- fixedWidth: true,
- onPostRender: createListBoxChangeHandler(items, 'fontsize'),
- onclick: function(e) {
- if (e.control.settings.value) {
- editor.execCommand('FontSize', false, e.control.settings.value);
- }
- }
- };
- });
- editor.addMenuItem('formats', {
- text: 'Formats',
- menu: formatMenu
- });
- }
- });
- // Included from: js/tinymce/classes/ui/GridLayout.js
- /**
- * GridLayout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This layout manager places controls in a grid.
- *
- * @setting {Number} spacing Spacing between controls.
- * @setting {Number} spacingH Horizontal spacing between controls.
- * @setting {Number} spacingV Vertical spacing between controls.
- * @setting {Number} columns Number of columns to use.
- * @setting {String/Array} alignH start|end|center|stretch or array of values for each column.
- * @setting {String/Array} alignV start|end|center|stretch or array of values for each column.
- * @setting {String} pack start|end
- *
- * @class tinymce.ui.GridLayout
- * @extends tinymce.ui.AbsoluteLayout
- */
- define("tinymce/ui/GridLayout", [
- "tinymce/ui/AbsoluteLayout"
- ], function(AbsoluteLayout) {
- "use strict";
- return AbsoluteLayout.extend({
- /**
- * Recalculates the positions of the controls in the specified container.
- *
- * @method recalc
- * @param {tinymce.ui.Container} container Container instance to recalc.
- */
- recalc: function(container) {
- var settings, rows, cols, items, contLayoutRect, width, height, rect,
- ctrlLayoutRect, ctrl, x, y, posX, posY, ctrlSettings, contPaddingBox, align, spacingH, spacingV, alignH, alignV, maxX, maxY,
- colWidths = [], rowHeights = [], ctrlMinWidth, ctrlMinHeight, availableWidth, availableHeight, reverseRows, idx;
- // Get layout settings
- settings = container.settings;
- items = container.items().filter(':visible');
- contLayoutRect = container.layoutRect();
- cols = settings.columns || Math.ceil(Math.sqrt(items.length));
- rows = Math.ceil(items.length / cols);
- spacingH = settings.spacingH || settings.spacing || 0;
- spacingV = settings.spacingV || settings.spacing || 0;
- alignH = settings.alignH || settings.align;
- alignV = settings.alignV || settings.align;
- contPaddingBox = container.paddingBox;
- reverseRows = 'reverseRows' in settings ? settings.reverseRows : container.isRtl();
- if (alignH && typeof alignH == "string") {
- alignH = [alignH];
- }
- if (alignV && typeof alignV == "string") {
- alignV = [alignV];
- }
- // Zero padd columnWidths
- for (x = 0; x < cols; x++) {
- colWidths.push(0);
- }
- // Zero padd rowHeights
- for (y = 0; y < rows; y++) {
- rowHeights.push(0);
- }
- // Calculate columnWidths and rowHeights
- for (y = 0; y < rows; y++) {
- for (x = 0; x < cols; x++) {
- ctrl = items[y * cols + x];
- // Out of bounds
- if (!ctrl) {
- break;
- }
- ctrlLayoutRect = ctrl.layoutRect();
- ctrlMinWidth = ctrlLayoutRect.minW;
- ctrlMinHeight = ctrlLayoutRect.minH;
- colWidths[x] = ctrlMinWidth > colWidths[x] ? ctrlMinWidth : colWidths[x];
- rowHeights[y] = ctrlMinHeight > rowHeights[y] ? ctrlMinHeight : rowHeights[y];
- }
- }
- // Calculate maxX
- availableWidth = contLayoutRect.innerW - contPaddingBox.left - contPaddingBox.right;
- for (maxX = 0, x = 0; x < cols; x++) {
- maxX += colWidths[x] + (x > 0 ? spacingH : 0);
- availableWidth -= (x > 0 ? spacingH : 0) + colWidths[x];
- }
- // Calculate maxY
- availableHeight = contLayoutRect.innerH - contPaddingBox.top - contPaddingBox.bottom;
- for (maxY = 0, y = 0; y < rows; y++) {
- maxY += rowHeights[y] + (y > 0 ? spacingV : 0);
- availableHeight -= (y > 0 ? spacingV : 0) + rowHeights[y];
- }
- maxX += contPaddingBox.left + contPaddingBox.right;
- maxY += contPaddingBox.top + contPaddingBox.bottom;
- // Calculate minW/minH
- rect = {};
- rect.minW = maxX + (contLayoutRect.w - contLayoutRect.innerW);
- rect.minH = maxY + (contLayoutRect.h - contLayoutRect.innerH);
- rect.contentW = rect.minW - contLayoutRect.deltaW;
- rect.contentH = rect.minH - contLayoutRect.deltaH;
- rect.minW = Math.min(rect.minW, contLayoutRect.maxW);
- rect.minH = Math.min(rect.minH, contLayoutRect.maxH);
- rect.minW = Math.max(rect.minW, contLayoutRect.startMinWidth);
- rect.minH = Math.max(rect.minH, contLayoutRect.startMinHeight);
- // Resize container container if minSize was changed
- if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
- rect.w = rect.minW;
- rect.h = rect.minH;
- container.layoutRect(rect);
- this.recalc(container);
- // Forced recalc for example if items are hidden/shown
- if (container._lastRect === null) {
- var parentCtrl = container.parent();
- if (parentCtrl) {
- parentCtrl._lastRect = null;
- parentCtrl.recalc();
- }
- }
- return;
- }
- // Update contentW/contentH so absEnd moves correctly
- if (contLayoutRect.autoResize) {
- rect = container.layoutRect(rect);
- rect.contentW = rect.minW - contLayoutRect.deltaW;
- rect.contentH = rect.minH - contLayoutRect.deltaH;
- }
- var flexV;
- if (settings.packV == 'start') {
- flexV = 0;
- } else {
- flexV = availableHeight > 0 ? Math.floor(availableHeight / rows) : 0;
- }
- // Calculate totalFlex
- var totalFlex = 0;
- var flexWidths = settings.flexWidths;
- if (flexWidths) {
- for (x = 0; x < flexWidths.length; x++) {
- totalFlex += flexWidths[x];
- }
- } else {
- totalFlex = cols;
- }
- // Calculate new column widths based on flex values
- var ratio = availableWidth / totalFlex;
- for (x = 0; x < cols; x++) {
- colWidths[x] += flexWidths ? flexWidths[x] * ratio : ratio;
- }
- // Move/resize controls
- posY = contPaddingBox.top;
- for (y = 0; y < rows; y++) {
- posX = contPaddingBox.left;
- height = rowHeights[y] + flexV;
- for (x = 0; x < cols; x++) {
- if (reverseRows) {
- idx = y * cols + cols - 1 - x;
- } else {
- idx = y * cols + x;
- }
- ctrl = items[idx];
- // No more controls to render then break
- if (!ctrl) {
- break;
- }
- // Get control settings and calculate x, y
- ctrlSettings = ctrl.settings;
- ctrlLayoutRect = ctrl.layoutRect();
- width = Math.max(colWidths[x], ctrlLayoutRect.startMinWidth);
- ctrlLayoutRect.x = posX;
- ctrlLayoutRect.y = posY;
- // Align control horizontal
- align = ctrlSettings.alignH || (alignH ? (alignH[x] || alignH[0]) : null);
- if (align == "center") {
- ctrlLayoutRect.x = posX + (width / 2) - (ctrlLayoutRect.w / 2);
- } else if (align == "right") {
- ctrlLayoutRect.x = posX + width - ctrlLayoutRect.w;
- } else if (align == "stretch") {
- ctrlLayoutRect.w = width;
- }
- // Align control vertical
- align = ctrlSettings.alignV || (alignV ? (alignV[x] || alignV[0]) : null);
- if (align == "center") {
- ctrlLayoutRect.y = posY + (height / 2) - (ctrlLayoutRect.h / 2);
- } else if (align == "bottom") {
- ctrlLayoutRect.y = posY + height - ctrlLayoutRect.h;
- } else if (align == "stretch") {
- ctrlLayoutRect.h = height;
- }
- ctrl.layoutRect(ctrlLayoutRect);
- posX += width + spacingH;
- if (ctrl.recalc) {
- ctrl.recalc();
- }
- }
- posY += height + spacingV;
- }
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Iframe.js
- /**
- * Iframe.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*jshint scripturl:true */
- /**
- * This class creates an iframe.
- *
- * @setting {String} url Url to open in the iframe.
- *
- * @-x-less Iframe.less
- * @class tinymce.ui.Iframe
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Iframe", [
- "tinymce/ui/Widget",
- "tinymce/util/Delay"
- ], function(Widget, Delay) {
- "use strict";
- return Widget.extend({
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this;
- self.classes.add('iframe');
- self.canFocus = false;
- /*eslint no-script-url:0 */
- return (
- '<iframe id="' + self._id + '" class="' + self.classes + '" tabindex="-1" src="' +
- (self.settings.url || "javascript:''") + '" frameborder="0"></iframe>'
- );
- },
- /**
- * Setter for the iframe source.
- *
- * @method src
- * @param {String} src Source URL for iframe.
- */
- src: function(src) {
- this.getEl().src = src;
- },
- /**
- * Inner HTML for the iframe.
- *
- * @method html
- * @param {String} html HTML string to set as HTML inside the iframe.
- * @param {function} callback Optional callback to execute when the iframe body is filled with contents.
- * @return {tinymce.ui.Iframe} Current iframe control.
- */
- html: function(html, callback) {
- var self = this, body = this.getEl().contentWindow.document.body;
- // Wait for iframe to initialize IE 10 takes time
- if (!body) {
- Delay.setTimeout(function() {
- self.html(html);
- });
- } else {
- body.innerHTML = html;
- if (callback) {
- callback();
- }
- }
- return this;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/InfoBox.js
- /**
- * InfoBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2016 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * ....
- *
- * @-x-less InfoBox.less
- * @class tinymce.ui.InfoBox
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/InfoBox", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- return Widget.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Boolean} multiline Multiline label.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- self.classes.add('widget').add('infobox');
- self.canFocus = false;
- },
- severity: function(level) {
- this.classes.remove('error');
- this.classes.remove('warning');
- this.classes.remove('success');
- this.classes.add(level);
- },
- help: function(state) {
- this.state.set('help', state);
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, prefix = self.classPrefix;
- return (
- '<div id="' + self._id + '" class="' + self.classes + '">' +
- '<div id="' + self._id + '-body">' +
- self.encode(self.state.get('text')) +
- '<button role="button" tabindex="-1">' +
- '<i class="' + prefix + 'ico ' + prefix + 'i-help"></i>' +
- '</button>' +
- '</div>' +
- '</div>'
- );
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:text', function(e) {
- self.getEl('body').firstChild.data = self.encode(e.value);
- if (self.state.get('rendered')) {
- self.updateLayoutRect();
- }
- });
- self.state.on('change:help', function(e) {
- self.classes.toggle('has-help', e.value);
- if (self.state.get('rendered')) {
- self.updateLayoutRect();
- }
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Label.js
- /**
- * Label.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class creates a label element. A label is a simple text control
- * that can be bound to other controls.
- *
- * @-x-less Label.less
- * @class tinymce.ui.Label
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Label", [
- "tinymce/ui/Widget",
- "tinymce/ui/DomUtils"
- ], function(Widget, DomUtils) {
- "use strict";
- return Widget.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Boolean} multiline Multiline label.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- self.classes.add('widget').add('label');
- self.canFocus = false;
- if (settings.multiline) {
- self.classes.add('autoscroll');
- }
- if (settings.strong) {
- self.classes.add('strong');
- }
- },
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, layoutRect = self._super();
- if (self.settings.multiline) {
- var size = DomUtils.getSize(self.getEl());
- // Check if the text fits within maxW if not then try word wrapping it
- if (size.width > layoutRect.maxW) {
- layoutRect.minW = layoutRect.maxW;
- self.classes.add('multiline');
- }
- self.getEl().style.width = layoutRect.minW + 'px';
- layoutRect.startMinH = layoutRect.h = layoutRect.minH = Math.min(layoutRect.maxH, DomUtils.getSize(self.getEl()).height);
- }
- return layoutRect;
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this;
- if (!self.settings.multiline) {
- self.getEl().style.lineHeight = self.layoutRect().h + 'px';
- }
- return self._super();
- },
- severity: function(level) {
- this.classes.remove('error');
- this.classes.remove('warning');
- this.classes.remove('success');
- this.classes.add(level);
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, targetCtrl, forName, forId = self.settings.forId;
- if (!forId && (forName = self.settings.forName)) {
- targetCtrl = self.getRoot().find('#' + forName)[0];
- if (targetCtrl) {
- forId = targetCtrl._id;
- }
- }
- if (forId) {
- return (
- '<label id="' + self._id + '" class="' + self.classes + '"' + (forId ? ' for="' + forId + '"' : '') + '>' +
- self.encode(self.state.get('text')) +
- '</label>'
- );
- }
- return (
- '<span id="' + self._id + '" class="' + self.classes + '">' +
- self.encode(self.state.get('text')) +
- '</span>'
- );
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:text', function(e) {
- self.innerHtml(self.encode(e.value));
- if (self.state.get('rendered')) {
- self.updateLayoutRect();
- }
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Toolbar.js
- /**
- * Toolbar.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new toolbar.
- *
- * @class tinymce.ui.Toolbar
- * @extends tinymce.ui.Container
- */
- define("tinymce/ui/Toolbar", [
- "tinymce/ui/Container"
- ], function(Container) {
- "use strict";
- return Container.extend({
- Defaults: {
- role: 'toolbar',
- layout: 'flow'
- },
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- self.classes.add('toolbar');
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this;
- self.items().each(function(ctrl) {
- ctrl.classes.add('toolbar-item');
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/MenuBar.js
- /**
- * MenuBar.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new menubar.
- *
- * @-x-less MenuBar.less
- * @class tinymce.ui.MenuBar
- * @extends tinymce.ui.Container
- */
- define("tinymce/ui/MenuBar", [
- "tinymce/ui/Toolbar"
- ], function(Toolbar) {
- "use strict";
- return Toolbar.extend({
- Defaults: {
- role: 'menubar',
- containerCls: 'menubar',
- ariaRoot: true,
- defaults: {
- type: 'menubutton'
- }
- }
- });
- });
- // Included from: js/tinymce/classes/ui/MenuButton.js
- /**
- * MenuButton.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new menu button.
- *
- * @-x-less MenuButton.less
- * @class tinymce.ui.MenuButton
- * @extends tinymce.ui.Button
- */
- define("tinymce/ui/MenuButton", [
- "tinymce/ui/Button",
- "tinymce/ui/Factory",
- "tinymce/ui/MenuBar"
- ], function(Button, Factory, MenuBar) {
- "use strict";
- // TODO: Maybe add as some global function
- function isChildOf(node, parent) {
- while (node) {
- if (parent === node) {
- return true;
- }
- node = node.parentNode;
- }
- return false;
- }
- var MenuButton = Button.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- var self = this;
- self._renderOpen = true;
- self._super(settings);
- settings = self.settings;
- self.classes.add('menubtn');
- if (settings.fixedWidth) {
- self.classes.add('fixed-width');
- }
- self.aria('haspopup', true);
- self.state.set('menu', settings.menu || self.render());
- },
- /**
- * Shows the menu for the button.
- *
- * @method showMenu
- */
- showMenu: function() {
- var self = this, menu;
- if (self.menu && self.menu.visible()) {
- return self.hideMenu();
- }
- if (!self.menu) {
- menu = self.state.get('menu') || [];
- // Is menu array then auto constuct menu control
- if (menu.length) {
- menu = {
- type: 'menu',
- items: menu
- };
- } else {
- menu.type = menu.type || 'menu';
- }
- if (!menu.renderTo) {
- self.menu = Factory.create(menu).parent(self).renderTo();
- } else {
- self.menu = menu.parent(self).show().renderTo();
- }
- self.fire('createmenu');
- self.menu.reflow();
- self.menu.on('cancel', function(e) {
- if (e.control.parent() === self.menu) {
- e.stopPropagation();
- self.focus();
- self.hideMenu();
- }
- });
- // Move focus to button when a menu item is selected/clicked
- self.menu.on('select', function() {
- self.focus();
- });
- self.menu.on('show hide', function(e) {
- if (e.control == self.menu) {
- self.activeMenu(e.type == 'show');
- }
- self.aria('expanded', e.type == 'show');
- }).fire('show');
- }
- self.menu.show();
- self.menu.layoutRect({w: self.layoutRect().w});
- self.menu.moveRel(self.getEl(), self.isRtl() ? ['br-tr', 'tr-br'] : ['bl-tl', 'tl-bl']);
- },
- /**
- * Hides the menu for the button.
- *
- * @method hideMenu
- */
- hideMenu: function() {
- var self = this;
- if (self.menu) {
- self.menu.items().each(function(item) {
- if (item.hideMenu) {
- item.hideMenu();
- }
- });
- self.menu.hide();
- }
- },
- /**
- * Sets the active menu state.
- *
- * @private
- */
- activeMenu: function(state) {
- this.classes.toggle('active', state);
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix;
- var icon = self.settings.icon, image, text = self.state.get('text'),
- textHtml = '';
- image = self.settings.image;
- if (image) {
- icon = 'none';
- // Support for [high dpi, low dpi] image sources
- if (typeof image != "string") {
- image = window.getSelection ? image[0] : image[1];
- }
- image = ' style="background-image: url(\'' + image + '\')"';
- } else {
- image = '';
- }
- if (text) {
- self.classes.add('btn-has-text');
- textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
- }
- icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
- self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
- return (
- '<div id="' + id + '" class="' + self.classes + '" tabindex="-1" aria-labelledby="' + id + '">' +
- '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
- (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
- textHtml +
- ' <i class="' + prefix + 'caret"></i>' +
- '</button>' +
- '</div>'
- );
- },
- /**
- * Gets invoked after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this;
- self.on('click', function(e) {
- if (e.control === self && isChildOf(e.target, self.getEl())) {
- self.showMenu();
- if (e.aria) {
- self.menu.items()[0].focus();
- }
- }
- });
- self.on('mouseenter', function(e) {
- var overCtrl = e.control, parent = self.parent(), hasVisibleSiblingMenu;
- if (overCtrl && parent && overCtrl instanceof MenuButton && overCtrl.parent() == parent) {
- parent.items().filter('MenuButton').each(function(ctrl) {
- if (ctrl.hideMenu && ctrl != overCtrl) {
- if (ctrl.menu && ctrl.menu.visible()) {
- hasVisibleSiblingMenu = true;
- }
- ctrl.hideMenu();
- }
- });
- if (hasVisibleSiblingMenu) {
- overCtrl.focus(); // Fix for: #5887
- overCtrl.showMenu();
- }
- }
- });
- return self._super();
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:menu', function() {
- if (self.menu) {
- self.menu.remove();
- }
- self.menu = null;
- });
- return self._super();
- },
- /**
- * Removes the control and it's menus.
- *
- * @method remove
- */
- remove: function() {
- this._super();
- if (this.menu) {
- this.menu.remove();
- }
- }
- });
- return MenuButton;
- });
- // Included from: js/tinymce/classes/ui/MenuItem.js
- /**
- * MenuItem.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new menu item.
- *
- * @-x-less MenuItem.less
- * @class tinymce.ui.MenuItem
- * @extends tinymce.ui.Control
- */
- define("tinymce/ui/MenuItem", [
- "tinymce/ui/Widget",
- "tinymce/ui/Factory",
- "tinymce/Env",
- "tinymce/util/Delay"
- ], function(Widget, Factory, Env, Delay) {
- "use strict";
- return Widget.extend({
- Defaults: {
- border: 0,
- role: 'menuitem'
- },
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Boolean} selectable Selectable menu.
- * @setting {Array} menu Submenu array with items.
- * @setting {String} shortcut Shortcut to display for menu item. Example: Ctrl+X
- */
- init: function(settings) {
- var self = this, text;
- self._super(settings);
- settings = self.settings;
- self.classes.add('menu-item');
- if (settings.menu) {
- self.classes.add('menu-item-expand');
- }
- if (settings.preview) {
- self.classes.add('menu-item-preview');
- }
- text = self.state.get('text');
- if (text === '-' || text === '|') {
- self.classes.add('menu-item-sep');
- self.aria('role', 'separator');
- self.state.set('text', '-');
- }
- if (settings.selectable) {
- self.aria('role', 'menuitemcheckbox');
- self.classes.add('menu-item-checkbox');
- settings.icon = 'selected';
- }
- if (!settings.preview && !settings.selectable) {
- self.classes.add('menu-item-normal');
- }
- self.on('mousedown', function(e) {
- e.preventDefault();
- });
- if (settings.menu && !settings.ariaHideMenu) {
- self.aria('haspopup', true);
- }
- },
- /**
- * Returns true/false if the menuitem has sub menu.
- *
- * @method hasMenus
- * @return {Boolean} True/false state if it has submenu.
- */
- hasMenus: function() {
- return !!this.settings.menu;
- },
- /**
- * Shows the menu for the menu item.
- *
- * @method showMenu
- */
- showMenu: function() {
- var self = this, settings = self.settings, menu, parent = self.parent();
- parent.items().each(function(ctrl) {
- if (ctrl !== self) {
- ctrl.hideMenu();
- }
- });
- if (settings.menu) {
- menu = self.menu;
- if (!menu) {
- menu = settings.menu;
- // Is menu array then auto constuct menu control
- if (menu.length) {
- menu = {
- type: 'menu',
- items: menu
- };
- } else {
- menu.type = menu.type || 'menu';
- }
- if (parent.settings.itemDefaults) {
- menu.itemDefaults = parent.settings.itemDefaults;
- }
- menu = self.menu = Factory.create(menu).parent(self).renderTo();
- menu.reflow();
- menu.on('cancel', function(e) {
- e.stopPropagation();
- self.focus();
- menu.hide();
- });
- menu.on('show hide', function(e) {
- e.control.items().each(function(ctrl) {
- ctrl.active(ctrl.settings.selected);
- });
- }).fire('show');
- menu.on('hide', function(e) {
- if (e.control === menu) {
- self.classes.remove('selected');
- }
- });
- menu.submenu = true;
- } else {
- menu.show();
- }
- menu._parentMenu = parent;
- menu.classes.add('menu-sub');
- var rel = menu.testMoveRel(
- self.getEl(),
- self.isRtl() ? ['tl-tr', 'bl-br', 'tr-tl', 'br-bl'] : ['tr-tl', 'br-bl', 'tl-tr', 'bl-br']
- );
- menu.moveRel(self.getEl(), rel);
- menu.rel = rel;
- rel = 'menu-sub-' + rel;
- menu.classes.remove(menu._lastRel).add(rel);
- menu._lastRel = rel;
- self.classes.add('selected');
- self.aria('expanded', true);
- }
- },
- /**
- * Hides the menu for the menu item.
- *
- * @method hideMenu
- */
- hideMenu: function() {
- var self = this;
- if (self.menu) {
- self.menu.items().each(function(item) {
- if (item.hideMenu) {
- item.hideMenu();
- }
- });
- self.menu.hide();
- self.aria('expanded', false);
- }
- return self;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, settings = self.settings, prefix = self.classPrefix, text = self.encode(self.state.get('text'));
- var icon = self.settings.icon, image = '', shortcut = settings.shortcut;
- // Converts shortcut format to Mac/PC variants
- function convertShortcut(shortcut) {
- var i, value, replace = {};
- if (Env.mac) {
- replace = {
- alt: '⌥',
- ctrl: '⌘',
- shift: '⇧',
- meta: '⌘'
- };
- } else {
- replace = {
- meta: 'Ctrl'
- };
- }
- shortcut = shortcut.split('+');
- for (i = 0; i < shortcut.length; i++) {
- value = replace[shortcut[i].toLowerCase()];
- if (value) {
- shortcut[i] = value;
- }
- }
- return shortcut.join('+');
- }
- if (icon) {
- self.parent().classes.add('menu-has-icons');
- }
- if (settings.image) {
- image = ' style="background-image: url(\'' + settings.image + '\')"';
- }
- if (shortcut) {
- shortcut = convertShortcut(shortcut);
- }
- icon = prefix + 'ico ' + prefix + 'i-' + (self.settings.icon || 'none');
- return (
- '<div id="' + id + '" class="' + self.classes + '" tabindex="-1">' +
- (text !== '-' ? '<i class="' + icon + '"' + image + '></i>\u00a0' : '') +
- (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
- (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
- (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
- '</div>'
- );
- },
- /**
- * Gets invoked after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this, settings = self.settings;
- var textStyle = settings.textStyle;
- if (typeof textStyle == "function") {
- textStyle = textStyle.call(this);
- }
- if (textStyle) {
- var textElm = self.getEl('text');
- if (textElm) {
- textElm.setAttribute('style', textStyle);
- }
- }
- self.on('mouseenter click', function(e) {
- if (e.control === self) {
- if (!settings.menu && e.type === 'click') {
- self.fire('select');
- // Edge will crash if you stress it see #2660
- Delay.requestAnimationFrame(function() {
- self.parent().hideAll();
- });
- } else {
- self.showMenu();
- if (e.aria) {
- self.menu.focus(true);
- }
- }
- }
- });
- self._super();
- return self;
- },
- hover: function() {
- var self = this;
- self.parent().items().each(function(ctrl) {
- ctrl.classes.remove('selected');
- });
- self.classes.toggle('selected', true);
- return self;
- },
- active: function(state) {
- if (typeof state != "undefined") {
- this.aria('checked', state);
- }
- return this._super(state);
- },
- /**
- * Removes the control and it's menus.
- *
- * @method remove
- */
- remove: function() {
- this._super();
- if (this.menu) {
- this.menu.remove();
- }
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Throbber.js
- /**
- * Throbber.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This class enables you to display a Throbber for any element.
- *
- * @-x-less Throbber.less
- * @class tinymce.ui.Throbber
- */
- define("tinymce/ui/Throbber", [
- "tinymce/dom/DomQuery",
- "tinymce/ui/Control",
- "tinymce/util/Delay"
- ], function($, Control, Delay) {
- "use strict";
- /**
- * Constructs a new throbber.
- *
- * @constructor
- * @param {Element} elm DOM Html element to display throbber in.
- * @param {Boolean} inline Optional true/false state if the throbber should be appended to end of element for infinite scroll.
- */
- return function(elm, inline) {
- var self = this, state, classPrefix = Control.classPrefix, timer;
- /**
- * Shows the throbber.
- *
- * @method show
- * @param {Number} [time] Time to wait before showing.
- * @param {function} [callback] Optional callback to execute when the throbber is shown.
- * @return {tinymce.ui.Throbber} Current throbber instance.
- */
- self.show = function(time, callback) {
- function render() {
- if (state) {
- $(elm).append(
- '<div class="' + classPrefix + 'throbber' + (inline ? ' ' + classPrefix + 'throbber-inline' : '') + '"></div>'
- );
- if (callback) {
- callback();
- }
- }
- }
- self.hide();
- state = true;
- if (time) {
- timer = Delay.setTimeout(render, time);
- } else {
- render();
- }
- return self;
- };
- /**
- * Hides the throbber.
- *
- * @method hide
- * @return {tinymce.ui.Throbber} Current throbber instance.
- */
- self.hide = function() {
- var child = elm.lastChild;
- Delay.clearTimeout(timer);
- if (child && child.className.indexOf('throbber') != -1) {
- child.parentNode.removeChild(child);
- }
- state = false;
- return self;
- };
- };
- });
- // Included from: js/tinymce/classes/ui/Menu.js
- /**
- * Menu.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new menu.
- *
- * @-x-less Menu.less
- * @class tinymce.ui.Menu
- * @extends tinymce.ui.FloatPanel
- */
- define("tinymce/ui/Menu", [
- "tinymce/ui/FloatPanel",
- "tinymce/ui/MenuItem",
- "tinymce/ui/Throbber",
- "tinymce/util/Tools"
- ], function(FloatPanel, MenuItem, Throbber, Tools) {
- "use strict";
- return FloatPanel.extend({
- Defaults: {
- defaultType: 'menuitem',
- border: 1,
- layout: 'stack',
- role: 'application',
- bodyRole: 'menu',
- ariaRoot: true
- },
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- */
- init: function(settings) {
- var self = this;
- settings.autohide = true;
- settings.constrainToViewport = true;
- if (typeof settings.items === 'function') {
- settings.itemsFactory = settings.items;
- settings.items = [];
- }
- if (settings.itemDefaults) {
- var items = settings.items, i = items.length;
- while (i--) {
- items[i] = Tools.extend({}, settings.itemDefaults, items[i]);
- }
- }
- self._super(settings);
- self.classes.add('menu');
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- this.classes.toggle('menu-align', true);
- this._super();
- this.getEl().style.height = '';
- this.getEl('body').style.height = '';
- return this;
- },
- /**
- * Hides/closes the menu.
- *
- * @method cancel
- */
- cancel: function() {
- var self = this;
- self.hideAll();
- self.fire('select');
- },
- /**
- * Loads new items from the factory items function.
- *
- * @method load
- */
- load: function() {
- var self = this, time, factory;
- function hideThrobber() {
- if (self.throbber) {
- self.throbber.hide();
- self.throbber = null;
- }
- }
- factory = self.settings.itemsFactory;
- if (!factory) {
- return;
- }
- if (!self.throbber) {
- self.throbber = new Throbber(self.getEl('body'), true);
- if (self.items().length === 0) {
- self.throbber.show();
- self.fire('loading');
- } else {
- self.throbber.show(100, function() {
- self.items().remove();
- self.fire('loading');
- });
- }
- self.on('hide close', hideThrobber);
- }
- self.requestTime = time = new Date().getTime();
- self.settings.itemsFactory(function(items) {
- if (items.length === 0) {
- self.hide();
- return;
- }
- if (self.requestTime !== time) {
- return;
- }
- self.getEl().style.width = '';
- self.getEl('body').style.width = '';
- hideThrobber();
- self.items().remove();
- self.getEl('body').innerHTML = '';
- self.add(items);
- self.renderNew();
- self.fire('loaded');
- });
- },
- /**
- * Hide menu and all sub menus.
- *
- * @method hideAll
- */
- hideAll: function() {
- var self = this;
- this.find('menuitem').exec('hideMenu');
- return self._super();
- },
- /**
- * Invoked before the menu is rendered.
- *
- * @method preRender
- */
- preRender: function() {
- var self = this;
- self.items().each(function(ctrl) {
- var settings = ctrl.settings;
- if (settings.icon || settings.image || settings.selectable) {
- self._hasIcons = true;
- return false;
- }
- });
- if (self.settings.itemsFactory) {
- self.on('postrender', function() {
- if (self.settings.itemsFactory) {
- self.load();
- }
- });
- }
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ListBox.js
- /**
- * ListBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new list box control.
- *
- * @-x-less ListBox.less
- * @class tinymce.ui.ListBox
- * @extends tinymce.ui.MenuButton
- */
- define("tinymce/ui/ListBox", [
- "tinymce/ui/MenuButton",
- "tinymce/ui/Menu"
- ], function(MenuButton, Menu) {
- "use strict";
- return MenuButton.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Array} values Array with values to add to list box.
- */
- init: function(settings) {
- var self = this, values, selected, selectedText, lastItemCtrl;
- function setSelected(menuValues) {
- // Try to find a selected value
- for (var i = 0; i < menuValues.length; i++) {
- selected = menuValues[i].selected || settings.value === menuValues[i].value;
- if (selected) {
- selectedText = selectedText || menuValues[i].text;
- self.state.set('value', menuValues[i].value);
- return true;
- }
- // If the value has a submenu, try to find the selected values in that menu
- if (menuValues[i].menu) {
- if (setSelected(menuValues[i].menu)) {
- return true;
- }
- }
- }
- }
- self._super(settings);
- settings = self.settings;
- self._values = values = settings.values;
- if (values) {
- if (typeof settings.value != "undefined") {
- setSelected(values);
- }
- // Default with first item
- if (!selected && values.length > 0) {
- selectedText = values[0].text;
- self.state.set('value', values[0].value);
- }
- self.state.set('menu', values);
- }
- self.state.set('text', settings.text || selectedText);
- self.classes.add('listbox');
- self.on('select', function(e) {
- var ctrl = e.control;
- if (lastItemCtrl) {
- e.lastControl = lastItemCtrl;
- }
- if (settings.multiple) {
- ctrl.active(!ctrl.active());
- } else {
- self.value(e.control.value());
- }
- lastItemCtrl = ctrl;
- });
- },
- /**
- * Getter/setter function for the control value.
- *
- * @method value
- * @param {String} [value] Value to be set.
- * @return {Boolean/tinymce.ui.ListBox} Value or self if it's a set operation.
- */
- bindStates: function() {
- var self = this;
- function activateMenuItemsByValue(menu, value) {
- if (menu instanceof Menu) {
- menu.items().each(function(ctrl) {
- if (!ctrl.hasMenus()) {
- ctrl.active(ctrl.value() === value);
- }
- });
- }
- }
- function getSelectedItem(menuValues, value) {
- var selectedItem;
- if (!menuValues) {
- return;
- }
- for (var i = 0; i < menuValues.length; i++) {
- if (menuValues[i].value === value) {
- return menuValues[i];
- }
- if (menuValues[i].menu) {
- selectedItem = getSelectedItem(menuValues[i].menu, value);
- if (selectedItem) {
- return selectedItem;
- }
- }
- }
- }
- self.on('show', function(e) {
- activateMenuItemsByValue(e.control, self.value());
- });
- self.state.on('change:value', function(e) {
- var selectedItem = getSelectedItem(self.state.get('menu'), e.value);
- if (selectedItem) {
- self.text(selectedItem.text);
- } else {
- self.text(self.settings.text);
- }
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Radio.js
- /**
- * Radio.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new radio button.
- *
- * @-x-less Radio.less
- * @class tinymce.ui.Radio
- * @extends tinymce.ui.Checkbox
- */
- define("tinymce/ui/Radio", [
- "tinymce/ui/Checkbox"
- ], function(Checkbox) {
- "use strict";
- return Checkbox.extend({
- Defaults: {
- classes: "radio",
- role: "radio"
- }
- });
- });
- // Included from: js/tinymce/classes/ui/ResizeHandle.js
- /**
- * ResizeHandle.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Renders a resize handle that fires ResizeStart, Resize and ResizeEnd events.
- *
- * @-x-less ResizeHandle.less
- * @class tinymce.ui.ResizeHandle
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/ResizeHandle", [
- "tinymce/ui/Widget",
- "tinymce/ui/DragHelper"
- ], function(Widget, DragHelper) {
- "use strict";
- return Widget.extend({
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, prefix = self.classPrefix;
- self.classes.add('resizehandle');
- if (self.settings.direction == "both") {
- self.classes.add('resizehandle-both');
- }
- self.canFocus = false;
- return (
- '<div id="' + self._id + '" class="' + self.classes + '">' +
- '<i class="' + prefix + 'ico ' + prefix + 'i-resize"></i>' +
- '</div>'
- );
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this;
- self._super();
- self.resizeDragHelper = new DragHelper(this._id, {
- start: function() {
- self.fire('ResizeStart');
- },
- drag: function(e) {
- if (self.settings.direction != "both") {
- e.deltaX = 0;
- }
- self.fire('Resize', e);
- },
- stop: function() {
- self.fire('ResizeEnd');
- }
- });
- },
- remove: function() {
- if (this.resizeDragHelper) {
- this.resizeDragHelper.destroy();
- }
- return this._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/SelectBox.js
- /**
- * SelectBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new select box control.
- *
- * @-x-less SelectBox.less
- * @class tinymce.ui.SelectBox
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/SelectBox", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- function createOptions(options) {
- var strOptions = '';
- if (options) {
- for (var i = 0; i < options.length; i++) {
- strOptions += '<option value="' + options[i] + '">' + options[i] + '</option>';
- }
- }
- return strOptions;
- }
- return Widget.extend({
- Defaults: {
- classes: "selectbox",
- role: "selectbox",
- options: []
- },
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Array} values Array with values to add to list box.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- if (self.settings.size) {
- self.size = self.settings.size;
- }
- if (self.settings.options) {
- self._options = self.settings.options;
- }
- self.on('keydown', function(e) {
- var rootControl;
- if (e.keyCode == 13) {
- e.preventDefault();
- // Find root control that we can do toJSON on
- self.parents().reverse().each(function(ctrl) {
- if (ctrl.toJSON) {
- rootControl = ctrl;
- return false;
- }
- });
- // Fire event on current text box with the serialized data of the whole form
- self.fire('submit', {data: rootControl.toJSON()});
- }
- });
- },
- /**
- * Getter/setter function for the options state.
- *
- * @method options
- * @param {Array} [state] State to be set.
- * @return {Array|tinymce.ui.SelectBox} Array of string options.
- */
- options: function(state) {
- if (!arguments.length) {
- return this.state.get('options');
- }
- this.state.set('options', state);
- return this;
- },
- renderHtml: function() {
- var self = this, options, size = '';
- options = createOptions(self._options);
- if (self.size) {
- size = ' size = "' + self.size + '"';
- }
- return (
- '<select id="' + self._id + '" class="' + self.classes + '"' + size + '>' +
- options +
- '</select>'
- );
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:options', function(e) {
- self.getEl().innerHTML = createOptions(e.value);
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Slider.js
- /**
- * Slider.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Slider control.
- *
- * @-x-less Slider.less
- * @class tinymce.ui.Slider
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Slider", [
- "tinymce/ui/Widget",
- "tinymce/ui/DragHelper",
- "tinymce/ui/DomUtils"
- ], function(Widget, DragHelper, DomUtils) {
- "use strict";
- function constrain(value, minVal, maxVal) {
- if (value < minVal) {
- value = minVal;
- }
- if (value > maxVal) {
- value = maxVal;
- }
- return value;
- }
- function setAriaProp(el, name, value) {
- el.setAttribute('aria-' + name, value);
- }
- function updateSliderHandle(ctrl, value) {
- var maxHandlePos, shortSizeName, sizeName, stylePosName, styleValue, handleEl;
- if (ctrl.settings.orientation == "v") {
- stylePosName = "top";
- sizeName = "height";
- shortSizeName = "h";
- } else {
- stylePosName = "left";
- sizeName = "width";
- shortSizeName = "w";
- }
- handleEl = ctrl.getEl('handle');
- maxHandlePos = (ctrl.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(handleEl)[sizeName];
- styleValue = (maxHandlePos * ((value - ctrl._minValue) / (ctrl._maxValue - ctrl._minValue))) + 'px';
- handleEl.style[stylePosName] = styleValue;
- handleEl.style.height = ctrl.layoutRect().h + 'px';
- setAriaProp(handleEl, 'valuenow', value);
- setAriaProp(handleEl, 'valuetext', '' + ctrl.settings.previewFilter(value));
- setAriaProp(handleEl, 'valuemin', ctrl._minValue);
- setAriaProp(handleEl, 'valuemax', ctrl._maxValue);
- }
- return Widget.extend({
- init: function(settings) {
- var self = this;
- if (!settings.previewFilter) {
- settings.previewFilter = function(value) {
- return Math.round(value * 100) / 100.0;
- };
- }
- self._super(settings);
- self.classes.add('slider');
- if (settings.orientation == "v") {
- self.classes.add('vertical');
- }
- self._minValue = settings.minValue || 0;
- self._maxValue = settings.maxValue || 100;
- self._initValue = self.state.get('value');
- },
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix;
- return (
- '<div id="' + id + '" class="' + self.classes + '">' +
- '<div id="' + id + '-handle" class="' + prefix + 'slider-handle" role="slider" tabindex="-1"></div>' +
- '</div>'
- );
- },
- reset: function() {
- this.value(this._initValue).repaint();
- },
- postRender: function() {
- var self = this, minValue, maxValue, screenCordName,
- stylePosName, sizeName, shortSizeName;
- function toFraction(min, max, val) {
- return (val + min) / (max - min);
- }
- function fromFraction(min, max, val) {
- return (val * (max - min)) - min;
- }
- function handleKeyboard(minValue, maxValue) {
- function alter(delta) {
- var value;
- value = self.value();
- value = fromFraction(minValue, maxValue, toFraction(minValue, maxValue, value) + (delta * 0.05));
- value = constrain(value, minValue, maxValue);
- self.value(value);
- self.fire('dragstart', {value: value});
- self.fire('drag', {value: value});
- self.fire('dragend', {value: value});
- }
- self.on('keydown', function(e) {
- switch (e.keyCode) {
- case 37:
- case 38:
- alter(-1);
- break;
- case 39:
- case 40:
- alter(1);
- break;
- }
- });
- }
- function handleDrag(minValue, maxValue, handleEl) {
- var startPos, startHandlePos, maxHandlePos, handlePos, value;
- self._dragHelper = new DragHelper(self._id, {
- handle: self._id + "-handle",
- start: function(e) {
- startPos = e[screenCordName];
- startHandlePos = parseInt(self.getEl('handle').style[stylePosName], 10);
- maxHandlePos = (self.layoutRect()[shortSizeName] || 100) - DomUtils.getSize(handleEl)[sizeName];
- self.fire('dragstart', {value: value});
- },
- drag: function(e) {
- var delta = e[screenCordName] - startPos;
- handlePos = constrain(startHandlePos + delta, 0, maxHandlePos);
- handleEl.style[stylePosName] = handlePos + 'px';
- value = minValue + (handlePos / maxHandlePos) * (maxValue - minValue);
- self.value(value);
- self.tooltip().text('' + self.settings.previewFilter(value)).show().moveRel(handleEl, 'bc tc');
- self.fire('drag', {value: value});
- },
- stop: function() {
- self.tooltip().hide();
- self.fire('dragend', {value: value});
- }
- });
- }
- minValue = self._minValue;
- maxValue = self._maxValue;
- if (self.settings.orientation == "v") {
- screenCordName = "screenY";
- stylePosName = "top";
- sizeName = "height";
- shortSizeName = "h";
- } else {
- screenCordName = "screenX";
- stylePosName = "left";
- sizeName = "width";
- shortSizeName = "w";
- }
- self._super();
- handleKeyboard(minValue, maxValue, self.getEl('handle'));
- handleDrag(minValue, maxValue, self.getEl('handle'));
- },
- repaint: function() {
- this._super();
- updateSliderHandle(this, this.value());
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:value', function(e) {
- updateSliderHandle(self, e.value);
- });
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/Spacer.js
- /**
- * Spacer.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a spacer. This control is used in flex layouts for example.
- *
- * @-x-less Spacer.less
- * @class tinymce.ui.Spacer
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/Spacer", [
- "tinymce/ui/Widget"
- ], function(Widget) {
- "use strict";
- return Widget.extend({
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this;
- self.classes.add('spacer');
- self.canFocus = false;
- return '<div id="' + self._id + '" class="' + self.classes + '"></div>';
- }
- });
- });
- // Included from: js/tinymce/classes/ui/SplitButton.js
- /**
- * SplitButton.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a split button.
- *
- * @-x-less SplitButton.less
- * @class tinymce.ui.SplitButton
- * @extends tinymce.ui.Button
- */
- define("tinymce/ui/SplitButton", [
- "tinymce/ui/MenuButton",
- "tinymce/ui/DomUtils",
- "tinymce/dom/DomQuery"
- ], function(MenuButton, DomUtils, $) {
- return MenuButton.extend({
- Defaults: {
- classes: "widget btn splitbtn",
- role: "button"
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, elm = self.getEl(), rect = self.layoutRect(), mainButtonElm, menuButtonElm;
- self._super();
- mainButtonElm = elm.firstChild;
- menuButtonElm = elm.lastChild;
- $(mainButtonElm).css({
- width: rect.w - DomUtils.getSize(menuButtonElm).width,
- height: rect.h - 2
- });
- $(menuButtonElm).css({
- height: rect.h - 2
- });
- return self;
- },
- /**
- * Sets the active menu state.
- *
- * @private
- */
- activeMenu: function(state) {
- var self = this;
- $(self.getEl().lastChild).toggleClass(self.classPrefix + 'active', state);
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, id = self._id, prefix = self.classPrefix, image;
- var icon = self.state.get('icon'), text = self.state.get('text'),
- textHtml = '';
- image = self.settings.image;
- if (image) {
- icon = 'none';
- // Support for [high dpi, low dpi] image sources
- if (typeof image != "string") {
- image = window.getSelection ? image[0] : image[1];
- }
- image = ' style="background-image: url(\'' + image + '\')"';
- } else {
- image = '';
- }
- icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
- if (text) {
- self.classes.add('btn-has-text');
- textHtml = '<span class="' + prefix + 'txt">' + self.encode(text) + '</span>';
- }
- return (
- '<div id="' + id + '" class="' + self.classes + '" role="button" tabindex="-1">' +
- '<button type="button" hidefocus="1" tabindex="-1">' +
- (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
- textHtml +
- '</button>' +
- '<button type="button" class="' + prefix + 'open" hidefocus="1" tabindex="-1">' +
- //(icon ? '<i class="' + icon + '"></i>' : '') +
- (self._menuBtnText ? (icon ? '\u00a0' : '') + self._menuBtnText : '') +
- ' <i class="' + prefix + 'caret"></i>' +
- '</button>' +
- '</div>'
- );
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this, onClickHandler = self.settings.onclick;
- self.on('click', function(e) {
- var node = e.target;
- if (e.control == this) {
- // Find clicks that is on the main button
- while (node) {
- if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
- e.stopImmediatePropagation();
- if (onClickHandler) {
- onClickHandler.call(this, e);
- }
- return;
- }
- node = node.parentNode;
- }
- }
- });
- delete self.settings.onclick;
- return self._super();
- }
- });
- });
- // Included from: js/tinymce/classes/ui/StackLayout.js
- /**
- * StackLayout.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This layout uses the browsers layout when the items are blocks.
- *
- * @-x-less StackLayout.less
- * @class tinymce.ui.StackLayout
- * @extends tinymce.ui.FlowLayout
- */
- define("tinymce/ui/StackLayout", [
- "tinymce/ui/FlowLayout"
- ], function(FlowLayout) {
- "use strict";
- return FlowLayout.extend({
- Defaults: {
- containerClass: 'stack-layout',
- controlClass: 'stack-layout-item',
- endClass: 'break'
- },
- isNative: function() {
- return true;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/TabPanel.js
- /**
- * TabPanel.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a tab panel control.
- *
- * @-x-less TabPanel.less
- * @class tinymce.ui.TabPanel
- * @extends tinymce.ui.Panel
- *
- * @setting {Number} activeTab Active tab index.
- */
- define("tinymce/ui/TabPanel", [
- "tinymce/ui/Panel",
- "tinymce/dom/DomQuery",
- "tinymce/ui/DomUtils"
- ], function(Panel, $, DomUtils) {
- "use strict";
- return Panel.extend({
- Defaults: {
- layout: 'absolute',
- defaults: {
- type: 'panel'
- }
- },
- /**
- * Activates the specified tab by index.
- *
- * @method activateTab
- * @param {Number} idx Index of the tab to activate.
- */
- activateTab: function(idx) {
- var activeTabElm;
- if (this.activeTabId) {
- activeTabElm = this.getEl(this.activeTabId);
- $(activeTabElm).removeClass(this.classPrefix + 'active');
- activeTabElm.setAttribute('aria-selected', "false");
- }
- this.activeTabId = 't' + idx;
- activeTabElm = this.getEl('t' + idx);
- activeTabElm.setAttribute('aria-selected', "true");
- $(activeTabElm).addClass(this.classPrefix + 'active');
- this.items()[idx].show().fire('showtab');
- this.reflow();
- this.items().each(function(item, i) {
- if (idx != i) {
- item.hide();
- }
- });
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, layout = self._layout, tabsHtml = '', prefix = self.classPrefix;
- self.preRender();
- layout.preRender(self);
- self.items().each(function(ctrl, i) {
- var id = self._id + '-t' + i;
- ctrl.aria('role', 'tabpanel');
- ctrl.aria('labelledby', id);
- tabsHtml += (
- '<div id="' + id + '" class="' + prefix + 'tab" ' +
- 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
- self.encode(ctrl.settings.title) +
- '</div>'
- );
- });
- return (
- '<div id="' + self._id + '" class="' + self.classes + '" hidefocus="1" tabindex="-1">' +
- '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
- tabsHtml +
- '</div>' +
- '<div id="' + self._id + '-body" class="' + self.bodyClasses + '">' +
- layout.renderHtml(self) +
- '</div>' +
- '</div>'
- );
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this;
- self._super();
- self.settings.activeTab = self.settings.activeTab || 0;
- self.activateTab(self.settings.activeTab);
- this.on('click', function(e) {
- var targetParent = e.target.parentNode;
- if (e.target.parentNode.id == self._id + '-head') {
- var i = targetParent.childNodes.length;
- while (i--) {
- if (targetParent.childNodes[i] == e.target) {
- self.activateTab(i);
- }
- }
- }
- });
- },
- /**
- * Initializes the current controls layout rect.
- * This will be executed by the layout managers to determine the
- * default minWidth/minHeight etc.
- *
- * @method initLayoutRect
- * @return {Object} Layout rect instance.
- */
- initLayoutRect: function() {
- var self = this, rect, minW, minH;
- minW = DomUtils.getSize(self.getEl('head')).width;
- minW = minW < 0 ? 0 : minW;
- minH = 0;
- self.items().each(function(item) {
- minW = Math.max(minW, item.layoutRect().minW);
- minH = Math.max(minH, item.layoutRect().minH);
- });
- self.items().each(function(ctrl) {
- ctrl.settings.x = 0;
- ctrl.settings.y = 0;
- ctrl.settings.w = minW;
- ctrl.settings.h = minH;
- ctrl.layoutRect({
- x: 0,
- y: 0,
- w: minW,
- h: minH
- });
- });
- var headH = DomUtils.getSize(self.getEl('head')).height;
- self.settings.minWidth = minW;
- self.settings.minHeight = minH + headH;
- rect = self._super();
- rect.deltaH += headH;
- rect.innerH = rect.h - rect.deltaH;
- return rect;
- }
- });
- });
- // Included from: js/tinymce/classes/ui/TextBox.js
- /**
- * TextBox.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * Creates a new textbox.
- *
- * @-x-less TextBox.less
- * @class tinymce.ui.TextBox
- * @extends tinymce.ui.Widget
- */
- define("tinymce/ui/TextBox", [
- "tinymce/ui/Widget",
- "tinymce/util/Tools",
- "tinymce/ui/DomUtils"
- ], function(Widget, Tools, DomUtils) {
- return Widget.extend({
- /**
- * Constructs a instance with the specified settings.
- *
- * @constructor
- * @param {Object} settings Name/value object with settings.
- * @setting {Boolean} multiline True if the textbox is a multiline control.
- * @setting {Number} maxLength Max length for the textbox.
- * @setting {Number} size Size of the textbox in characters.
- */
- init: function(settings) {
- var self = this;
- self._super(settings);
- self.classes.add('textbox');
- if (settings.multiline) {
- self.classes.add('multiline');
- } else {
- self.on('keydown', function(e) {
- var rootControl;
- if (e.keyCode == 13) {
- e.preventDefault();
- // Find root control that we can do toJSON on
- self.parents().reverse().each(function(ctrl) {
- if (ctrl.toJSON) {
- rootControl = ctrl;
- return false;
- }
- });
- // Fire event on current text box with the serialized data of the whole form
- self.fire('submit', {data: rootControl.toJSON()});
- }
- });
- self.on('keyup', function(e) {
- self.state.set('value', e.target.value);
- });
- }
- },
- /**
- * Repaints the control after a layout operation.
- *
- * @method repaint
- */
- repaint: function() {
- var self = this, style, rect, borderBox, borderW, borderH = 0, lastRepaintRect;
- style = self.getEl().style;
- rect = self._layoutRect;
- lastRepaintRect = self._lastRepaintRect || {};
- // Detect old IE 7+8 add lineHeight to align caret vertically in the middle
- var doc = document;
- if (!self.settings.multiline && doc.all && (!doc.documentMode || doc.documentMode <= 8)) {
- style.lineHeight = (rect.h - borderH) + 'px';
- }
- borderBox = self.borderBox;
- borderW = borderBox.left + borderBox.right + 8;
- borderH = borderBox.top + borderBox.bottom + (self.settings.multiline ? 8 : 0);
- if (rect.x !== lastRepaintRect.x) {
- style.left = rect.x + 'px';
- lastRepaintRect.x = rect.x;
- }
- if (rect.y !== lastRepaintRect.y) {
- style.top = rect.y + 'px';
- lastRepaintRect.y = rect.y;
- }
- if (rect.w !== lastRepaintRect.w) {
- style.width = (rect.w - borderW) + 'px';
- lastRepaintRect.w = rect.w;
- }
- if (rect.h !== lastRepaintRect.h) {
- style.height = (rect.h - borderH) + 'px';
- lastRepaintRect.h = rect.h;
- }
- self._lastRepaintRect = lastRepaintRect;
- self.fire('repaint', {}, false);
- return self;
- },
- /**
- * Renders the control as a HTML string.
- *
- * @method renderHtml
- * @return {String} HTML representing the control.
- */
- renderHtml: function() {
- var self = this, settings = self.settings, attrs, elm;
- attrs = {
- id: self._id,
- hidefocus: '1'
- };
- Tools.each([
- 'rows', 'spellcheck', 'maxLength', 'size', 'readonly', 'min',
- 'max', 'step', 'list', 'pattern', 'placeholder', 'required', 'multiple'
- ], function(name) {
- attrs[name] = settings[name];
- });
- if (self.disabled()) {
- attrs.disabled = 'disabled';
- }
- if (settings.subtype) {
- attrs.type = settings.subtype;
- }
- elm = DomUtils.create(settings.multiline ? 'textarea' : 'input', attrs);
- elm.value = self.state.get('value');
- elm.className = self.classes;
- return elm.outerHTML;
- },
- value: function(value) {
- if (arguments.length) {
- this.state.set('value', value);
- return this;
- }
- // Make sure the real state is in sync
- if (this.state.get('rendered')) {
- this.state.set('value', this.getEl().value);
- }
- return this.state.get('value');
- },
- /**
- * Called after the control has been rendered.
- *
- * @method postRender
- */
- postRender: function() {
- var self = this;
- self.getEl().value = self.state.get('value');
- self._super();
- self.$el.on('change', function(e) {
- self.state.set('value', e.target.value);
- self.fire('change', e);
- });
- },
- bindStates: function() {
- var self = this;
- self.state.on('change:value', function(e) {
- if (self.getEl().value != e.value) {
- self.getEl().value = e.value;
- }
- });
- self.state.on('change:disabled', function(e) {
- self.getEl().disabled = e.value;
- });
- return self._super();
- },
- remove: function() {
- this.$el.off();
- this._super();
- }
- });
- });
- // Included from: js/tinymce/classes/Register.js
- /**
- * Register.js
- *
- * Released under LGPL License.
- * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /**
- * This registers tinymce in common module loaders.
- *
- * @private
- * @class tinymce.Register
- */
- define("tinymce/Register", [
- ], function() {
- /*eslint consistent-this: 0 */
- var context = this || window;
- var tinymce = function() {
- return context.tinymce;
- };
- if (typeof context.define === "function") {
- // Bolt
- if (!context.define.amd) {
- context.define("ephox/tinymce", [], tinymce);
- }
- }
- return {};
- });
- expose(["tinymce/geom/Rect","tinymce/util/Promise","tinymce/util/Delay","tinymce/Env","tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/util/Tools","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/html/Entities","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/dom/RangeUtils","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/BookmarkManager","tinymce/dom/Selection","tinymce/Formatter","tinymce/UndoManager","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/util/EventDispatcher","tinymce/util/Observable","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/ReflowQueue","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Progress","tinymce/ui/Notification","tinymce/NotificationManager","tinymce/EditorObservable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/ComboBox","tinymce/ui/ColorBox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/util/Color","tinymce/ui/ColorPicker","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/InfoBox","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/MenuItem","tinymce/ui/Throbber","tinymce/ui/Menu","tinymce/ui/ListBox","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/SelectBox","tinymce/ui/Slider","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox"]);
- })(this);
|