12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542 |
- /*
- Redactor 10.2.5
- Updated: October 1, 2015
- http://imperavi.com/redactor/
- Copyright (c) 2009-2015, Imperavi LLC.
- License: http://imperavi.com/redactor/license/
- Usage: $('#content').redactor();
- */
- (function($)
- {
- 'use strict';
- if (!Function.prototype.bind)
- {
- Function.prototype.bind = function(scope)
- {
- var fn = this;
- return function()
- {
- return fn.apply(scope);
- };
- };
- }
- var uuid = 0;
- // Plugin
- $.fn.redactor = function(options)
- {
- var val = [];
- var args = Array.prototype.slice.call(arguments, 1);
- if (typeof options === 'string')
- {
- this.each(function()
- {
- var instance = $.data(this, 'redactor');
- var func;
- if (options.search(/\./) != '-1')
- {
- func = options.split('.');
- if (typeof instance[func[0]] != 'undefined')
- {
- func = instance[func[0]][func[1]];
- }
- }
- else
- {
- func = instance[options];
- }
- if (typeof instance !== 'undefined' && $.isFunction(func))
- {
- var methodVal = func.apply(instance, args);
- if (methodVal !== undefined && methodVal !== instance)
- {
- val.push(methodVal);
- }
- }
- else
- {
- $.error('No such method "' + options + '" for Redactor');
- }
- });
- }
- else
- {
- this.each(function()
- {
- $.data(this, 'redactor', {});
- $.data(this, 'redactor', Redactor(this, options));
- });
- }
- if (val.length === 0) return this;
- else if (val.length === 1) return val[0];
- else return val;
- };
- // Initialization
- function Redactor(el, options)
- {
- return new Redactor.prototype.init(el, options);
- }
- // Functionality
- $.Redactor = Redactor;
- $.Redactor.VERSION = '10.2.5';
- $.Redactor.modules = ['alignment', 'autosave', 'block', 'buffer', 'build', 'button',
- 'caret', 'clean', 'code', 'core', 'dropdown', 'file', 'focus',
- 'image', 'indent', 'inline', 'insert', 'keydown', 'keyup',
- 'lang', 'line', 'link', 'linkify', 'list', 'modal', 'observe', 'paragraphize',
- 'paste', 'placeholder', 'progress', 'selection', 'shortcuts',
- 'tabifier', 'tidy', 'toolbar', 'upload', 'utils'];
- $.Redactor.opts = {
- // settings
- lang: 'en',
- direction: 'ltr', // ltr or rtl
- plugins: false, // array
- focus: false,
- focusEnd: false,
- placeholder: false,
- visual: true,
- tabindex: false,
- minHeight: false,
- maxHeight: false,
- linebreaks: false,
- replaceDivs: true,
- paragraphize: true,
- cleanStyleOnEnter: false,
- enterKey: true,
- cleanOnPaste: true,
- cleanSpaces: true,
- pastePlainText: false,
- autosave: false, // false or url
- autosaveName: false,
- autosaveInterval: 60, // seconds
- autosaveOnChange: false,
- autosaveFields: false,
- linkTooltip: true,
- linkProtocol: 'http',
- linkNofollow: false,
- linkSize: 50,
- imageEditable: true,
- imageLink: true,
- imagePosition: true,
- imageFloatMargin: '10px',
- imageResizable: true,
- imageUpload: null,
- imageUploadParam: 'file',
- uploadImageField: false,
- dragImageUpload: true,
- fileUpload: null,
- fileUploadParam: 'file',
- dragFileUpload: true,
- s3: false,
- convertLinks: true,
- convertUrlLinks: true,
- convertImageLinks: true,
- convertVideoLinks: true,
- preSpaces: 4, // or false
- tabAsSpaces: false, // true or number of spaces
- tabKey: true,
- scrollTarget: false,
- toolbar: true,
- toolbarFixed: true,
- toolbarFixedTarget: document,
- toolbarFixedTopOffset: 0, // pixels
- toolbarExternal: false, // ID selector
- toolbarOverflow: false,
- source: true,
- buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist',
- 'outdent', 'indent', 'image', 'file', 'link', 'alignment', 'horizontalrule'], // + 'underline'
- buttonsHide: [],
- buttonsHideOnMobile: [],
- formatting: ['p', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
- formattingAdd: false,
- tabifier: true,
- deniedTags: ['script', 'style'],
- allowedTags: false, // or array
- paragraphizeBlocks: ['table', 'div', 'pre', 'form', 'ul', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dl', 'blockquote', 'figcaption',
- 'address', 'section', 'header', 'footer', 'aside', 'article', 'object', 'style', 'script', 'iframe', 'select', 'input', 'textarea',
- 'button', 'option', 'map', 'area', 'math', 'hr', 'fieldset', 'legend', 'hgroup', 'nav', 'figure', 'details', 'menu', 'summary', 'p'],
- removeComments: false,
- replaceTags: [
- ['strike', 'del'],
- ['b', 'strong']
- ],
- replaceStyles: [
- ['font-weight:\\s?bold', "strong"],
- ['font-style:\\s?italic', "em"],
- ['text-decoration:\\s?underline', "u"],
- ['text-decoration:\\s?line-through', 'del']
- ],
- removeDataAttr: false,
- removeAttr: false, // or multi array
- allowedAttr: false, // or multi array
- removeWithoutAttr: ['span'], // or false
- removeEmpty: ['p'], // or false;
- activeButtons: ['deleted', 'italic', 'bold', 'underline', 'unorderedlist', 'orderedlist',
- 'alignleft', 'aligncenter', 'alignright', 'justify'],
- activeButtonsStates: {
- b: 'bold',
- strong: 'bold',
- i: 'italic',
- em: 'italic',
- del: 'deleted',
- strike: 'deleted',
- ul: 'unorderedlist',
- ol: 'orderedlist',
- u: 'underline'
- },
- shortcuts: {
- 'ctrl+shift+m, meta+shift+m': { func: 'inline.removeFormat' },
- 'ctrl+b, meta+b': { func: 'inline.format', params: ['bold'] },
- 'ctrl+i, meta+i': { func: 'inline.format', params: ['italic'] },
- 'ctrl+h, meta+h': { func: 'inline.format', params: ['superscript'] },
- 'ctrl+l, meta+l': { func: 'inline.format', params: ['subscript'] },
- 'ctrl+k, meta+k': { func: 'link.show' },
- 'ctrl+shift+7': { func: 'list.toggle', params: ['orderedlist'] },
- 'ctrl+shift+8': { func: 'list.toggle', params: ['unorderedlist'] }
- },
- shortcutsAdd: false,
- // private
- buffer: [],
- rebuffer: [],
- emptyHtml: '<p>​</p>',
- invisibleSpace: '​',
- imageTypes: ['image/png', 'image/jpeg', 'image/gif'],
- indentValue: 20,
- verifiedTags: ['a', 'img', 'b', 'strong', 'sub', 'sup', 'i', 'em', 'u', 'small', 'strike', 'del', 'cite', 'ul', 'ol', 'li'], // and for span tag special rule
- inlineTags: ['strong', 'b', 'u', 'em', 'i', 'code', 'del', 'ins', 'samp', 'kbd', 'sup', 'sub', 'mark', 'var', 'cite', 'small'],
- alignmentTags: ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'DL', 'DT', 'DD', 'DIV', 'TD', 'BLOCKQUOTE', 'OUTPUT', 'FIGCAPTION', 'ADDRESS', 'SECTION', 'HEADER', 'FOOTER', 'ASIDE', 'ARTICLE'],
- blockLevelElements: ['PRE', 'UL', 'OL', 'LI'],
- highContrast: false,
- observe: {
- dropdowns: []
- },
- // lang
- langs: {
- en: {
- html: 'HTML',
- video: 'Insert Video',
- image: 'Insert Image',
- table: 'Table',
- link: 'Link',
- link_insert: 'Insert link',
- link_edit: 'Edit link',
- unlink: 'Unlink',
- formatting: 'Formatting',
- paragraph: 'Normal text',
- quote: 'Quote',
- code: 'Code',
- header1: 'Header 1',
- header2: 'Header 2',
- header3: 'Header 3',
- header4: 'Header 4',
- header5: 'Header 5',
- bold: 'Bold',
- italic: 'Italic',
- fontcolor: 'Font Color',
- backcolor: 'Back Color',
- unorderedlist: 'Unordered List',
- orderedlist: 'Ordered List',
- outdent: 'Outdent',
- indent: 'Indent',
- cancel: 'Cancel',
- insert: 'Insert',
- save: 'Save',
- _delete: 'Delete',
- insert_table: 'Insert Table',
- insert_row_above: 'Add Row Above',
- insert_row_below: 'Add Row Below',
- insert_column_left: 'Add Column Left',
- insert_column_right: 'Add Column Right',
- delete_column: 'Delete Column',
- delete_row: 'Delete Row',
- delete_table: 'Delete Table',
- rows: 'Rows',
- columns: 'Columns',
- add_head: 'Add Head',
- delete_head: 'Delete Head',
- title: 'Title',
- image_position: 'Position',
- none: 'None',
- left: 'Left',
- right: 'Right',
- center: 'Center',
- image_web_link: 'Image Web Link',
- text: 'Text',
- mailto: 'Email',
- web: 'URL',
- video_html_code: 'Video Embed Code or Youtube/Vimeo Link',
- file: 'Insert File',
- upload: 'Upload',
- download: 'Download',
- choose: 'Choose',
- or_choose: 'Or choose',
- drop_file_here: 'Drop file here',
- align_left: 'Align text to the left',
- align_center: 'Center text',
- align_right: 'Align text to the right',
- align_justify: 'Justify text',
- horizontalrule: 'Insert Horizontal Rule',
- deleted: 'Deleted',
- anchor: 'Anchor',
- link_new_tab: 'Open link in new tab',
- underline: 'Underline',
- alignment: 'Alignment',
- filename: 'Name (optional)',
- edit: 'Edit',
- upload_label: 'Drop file here or '
- }
- },
- linkify: {
- regexps: {
- youtube: /https?:\/\/(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube\.com\S*[^\w\-\s])([\w\-]{11})(?=[^\w\-]|$)(?![?=&+%\w.\-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/ig,
- vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
- image: /((https?|www)[^\s]+\.)(jpe?g|png|gif)(\?[^\s-]+)?/ig,
- url: /(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})/ig,
- }
- },
- codemirror: false
- };
- // Functionality
- Redactor.fn = $.Redactor.prototype = {
- keyCode: {
- BACKSPACE: 8,
- DELETE: 46,
- UP: 38,
- DOWN: 40,
- ENTER: 13,
- SPACE: 32,
- ESC: 27,
- TAB: 9,
- CTRL: 17,
- META: 91,
- SHIFT: 16,
- ALT: 18,
- RIGHT: 39,
- LEFT: 37,
- LEFT_WIN: 91
- },
- // Initialization
- init: function(el, options)
- {
- this.$element = $(el);
- this.uuid = uuid++;
- // if paste event detected = true
- this.rtePaste = false;
- this.$pasteBox = false;
- this.loadOptions(options);
- this.loadModules();
- // formatting storage
- this.formatting = {};
- // block level tags
- $.merge(this.opts.blockLevelElements, this.opts.alignmentTags);
- this.reIsBlock = new RegExp('^(' + this.opts.blockLevelElements.join('|' ) + ')$', 'i');
- // setup allowed and denied tags
- this.tidy.setupAllowed();
- // setup denied tags
- if (this.opts.deniedTags !== false)
- {
- var tags = ['html', 'head', 'link', 'body', 'meta', 'applet'];
- for (var i = 0; i < tags.length; i++)
- {
- this.opts.deniedTags.push(tags[i]);
- }
- }
- // load lang
- this.lang.load();
- // extend shortcuts
- $.extend(this.opts.shortcuts, this.opts.shortcutsAdd);
- // start callback
- this.core.setCallback('start');
- // build
- this.start = true;
- this.build.run();
- },
- loadOptions: function(options)
- {
- this.opts = $.extend(
- {},
- $.extend(true, {}, $.Redactor.opts),
- this.$element.data(),
- options
- );
- },
- getModuleMethods: function(object)
- {
- return Object.getOwnPropertyNames(object).filter(function(property)
- {
- return typeof object[property] == 'function';
- });
- },
- loadModules: function()
- {
- var len = $.Redactor.modules.length;
- for (var i = 0; i < len; i++)
- {
- this.bindModuleMethods($.Redactor.modules[i]);
- }
- },
- bindModuleMethods: function(module)
- {
- if (typeof this[module] == 'undefined') return;
- // init module
- this[module] = this[module]();
- var methods = this.getModuleMethods(this[module]);
- var len = methods.length;
- // bind methods
- for (var z = 0; z < len; z++)
- {
- this[module][methods[z]] = this[module][methods[z]].bind(this);
- }
- },
- alignment: function()
- {
- return {
- left: function()
- {
- this.alignment.set('');
- },
- right: function()
- {
- this.alignment.set('right');
- },
- center: function()
- {
- this.alignment.set('center');
- },
- justify: function()
- {
- this.alignment.set('justify');
- },
- set: function(type)
- {
- // focus
- if (!this.utils.browser('msie') && !this.opts.linebreaks)
- {
- this.$editor.focus();
- }
- // get blocks
- this.alignment.blocks = this.selection.getBlocks();
- this.alignment.type = type;
- this.buffer.set();
- this.selection.save();
- // set alignment
- if (this.alignment.isLinebreaksOrNoBlocks())
- {
- this.alignment.setText();
- }
- else
- {
- this.alignment.setBlocks();
- }
- // sync
- this.selection.restore();
- this.code.sync();
- },
- setText: function()
- {
- var wrapper = this.selection.wrap('div');
- $(wrapper).attr('data-tagblock', 'redactor').css('text-align', this.alignment.type);
- },
- setBlocks: function()
- {
- $.each(this.alignment.blocks, $.proxy(function(i, el)
- {
- var $el = this.utils.getAlignmentElement(el);
- if (!$el) return;
- if (this.alignment.isNeedReplaceElement($el))
- {
- this.alignment.replaceElement($el);
- }
- else
- {
- this.alignment.alignElement($el);
- }
- }, this));
- },
- isLinebreaksOrNoBlocks: function()
- {
- return (this.opts.linebreaks && this.alignment.blocks[0] === false);
- },
- isNeedReplaceElement: function($el)
- {
- return (this.alignment.type === '' && typeof($el.data('tagblock')) !== 'undefined');
- },
- replaceElement: function($el)
- {
- $el.replaceWith($el.html());
- },
- alignElement: function($el)
- {
- $el.css('text-align', this.alignment.type);
- this.utils.removeEmptyAttr($el, 'style');
- }
- };
- },
- autosave: function()
- {
- return {
- html: false,
- enable: function()
- {
- if (!this.opts.autosave) return;
- this.autosave.name = (this.opts.autosaveName) ? this.opts.autosaveName : this.$textarea.attr('name');
- if (this.opts.autosaveOnChange) return;
- this.autosaveInterval = setInterval(this.autosave.load, this.opts.autosaveInterval * 1000);
- },
- onChange: function()
- {
- if (!this.opts.autosaveOnChange) return;
- this.autosave.load();
- },
- load: function()
- {
- if (!this.opts.autosave) return;
- this.autosave.source = this.code.get();
- if (this.autosave.html === this.autosave.source) return;
- // data
- var data = {};
- data['name'] = this.autosave.name;
- data[this.autosave.name] = this.autosave.source;
- data = this.autosave.getHiddenFields(data);
- // ajax
- var jsxhr = $.ajax({
- url: this.opts.autosave,
- type: 'post',
- data: data
- });
- jsxhr.done(this.autosave.success);
- },
- getHiddenFields: function(data)
- {
- if (this.opts.autosaveFields === false || typeof this.opts.autosaveFields !== 'object')
- {
- return data;
- }
- $.each(this.opts.autosaveFields, $.proxy(function(k, v)
- {
- if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
- data[k] = v;
- }, this));
- return data;
- },
- success: function(data)
- {
- var json;
- try
- {
- json = $.parseJSON(data);
- }
- catch(e)
- {
- //data has already been parsed
- json = data;
- }
- var callbackName = (typeof json.error == 'undefined') ? 'autosave' : 'autosaveError';
- this.core.setCallback(callbackName, this.autosave.name, json);
- this.autosave.html = this.autosave.source;
- },
- disable: function()
- {
- clearInterval(this.autosaveInterval);
- }
- };
- },
- block: function()
- {
- return {
- formatting: function(name)
- {
- this.block.clearStyle = false;
- var type, value;
- if (typeof this.formatting[name].data != 'undefined') type = 'data';
- else if (typeof this.formatting[name].attr != 'undefined') type = 'attr';
- else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class';
- if (typeof this.formatting[name].clear != 'undefined')
- {
- this.block.clearStyle = true;
- }
- if (type) value = this.formatting[name][type];
- this.block.format(this.formatting[name].tag, type, value);
- },
- format: function(tag, type, value)
- {
- if (tag == 'quote') tag = 'blockquote';
- var formatTags = ['p', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
- if ($.inArray(tag, formatTags) == -1) return;
- this.block.isRemoveInline = (tag == 'pre' || tag.search(/h[1-6]/i) != -1);
- // focus
- if (!this.utils.browser('msie')) this.$editor.focus();
- var html = $.trim(this.$editor.html());
- this.block.isEmpty = this.utils.isEmpty(html);
- // FF focus
- if (this.utils.browser('mozilla') && !this.focus.isFocused())
- {
- if (this.block.isEmpty)
- {
- var $first;
- if (!this.opts.linebreaks)
- {
- $first = this.$editor.children().first();
- this.caret.setEnd($first);
- }
- }
- }
- this.block.blocks = this.selection.getBlocks();
- this.block.blocksSize = this.block.blocks.length;
- this.block.type = type;
- this.block.value = value;
- this.buffer.set();
- this.selection.save();
- this.block.set(tag);
- this.selection.restore();
- this.code.sync();
- this.observe.load();
- },
- set: function(tag)
- {
- this.selection.get();
- this.block.containerTag = this.range.commonAncestorContainer.tagName;
- if (this.range.collapsed)
- {
- this.block.setCollapsed(tag);
- }
- else
- {
- this.block.setMultiple(tag);
- }
- },
- setCollapsed: function(tag)
- {
- if (this.opts.linebreaks && this.block.isEmpty && tag != 'p')
- {
- var node = document.createElement(tag);
- this.$editor.html(node);
- this.caret.setEnd(node);
- return;
- }
- var block = this.block.blocks[0];
- if (block === false) return;
- if (block.tagName == 'LI')
- {
- if (tag != 'blockquote') return;
- this.block.formatListToBlockquote();
- return;
- }
- var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
- if (isContainerTable && !this.opts.linebreaks)
- {
- document.execCommand('formatblock', false, '<' + tag + '>');
- block = this.selection.getBlock();
- this.block.toggle($(block));
- }
- else if (block.tagName.toLowerCase() != tag)
- {
- if (this.opts.linebreaks && tag == 'p')
- {
- $(block).append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
- var $formatted = this.utils.replaceToTag(block, tag);
- this.block.toggle($formatted);
- if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
- if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
- if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
- this.block.formatTableWrapping($formatted);
- }
- }
- else if (tag == 'blockquote' && block.tagName.toLowerCase() == tag)
- {
- // blockquote off
- if (this.opts.linebreaks)
- {
- $(block).append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
- var $el = this.utils.replaceToTag(block, 'p');
- this.block.toggle($el);
- }
- }
- else if (block.tagName.toLowerCase() == tag)
- {
- this.block.toggle($(block));
- }
- if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
- {
- $(block).removeAttr('class').removeAttr('style');
- }
- },
- setMultiple: function(tag)
- {
- var block = this.block.blocks[0];
- var isContainerTable = (this.block.containerTag == 'TD' || this.block.containerTag == 'TH');
- if (block !== false && this.block.blocksSize === 1)
- {
- if (block.tagName.toLowerCase() == tag && tag == 'blockquote')
- {
- // blockquote off
- if (this.opts.linebreaks)
- {
- $(block).append('<br>');
- this.utils.replaceWithContents(block);
- }
- else
- {
- var $el = this.utils.replaceToTag(block, 'p');
- this.block.toggle($el);
- }
- }
- else if (block.tagName == 'LI')
- {
- if (tag != 'blockquote') return;
- this.block.formatListToBlockquote();
- }
- else if (this.block.containerTag == 'BLOCKQUOTE')
- {
- this.block.formatBlockquote(tag);
- }
- else if (this.opts.linebreaks && ((isContainerTable) || (this.range.commonAncestorContainer != block)))
- {
- this.block.formatWrap(tag);
- }
- else
- {
- if (this.opts.linebreaks && tag == 'p')
- {
- $(block).prepend('<br>').append('<br>');
- this.utils.replaceWithContents(block);
- }
- else if (block.tagName === 'TD')
- {
- this.block.formatWrap(tag);
- }
- else
- {
- var $formatted = this.utils.replaceToTag(block, tag);
- this.block.toggle($formatted);
- if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
- if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
- }
- }
- }
- else
- {
- if (this.opts.linebreaks || tag != 'p')
- {
- if (tag == 'blockquote')
- {
- var count = 0;
- for (var i = 0; i < this.block.blocksSize; i++)
- {
- if (this.block.blocks[i].tagName == 'BLOCKQUOTE') count++;
- }
- // only blockquote selected
- if (count == this.block.blocksSize)
- {
- $.each(this.block.blocks, $.proxy(function(i,s)
- {
- var $formatted = false;
- if (this.opts.linebreaks)
- {
- $(s).prepend('<br>').append('<br>');
- $formatted = this.utils.replaceWithContents(s);
- }
- else
- {
- $formatted = this.utils.replaceToTag(s, 'p');
- }
- if ($formatted && typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
- {
- $formatted.removeAttr('class').removeAttr('style');
- }
- }, this));
- return;
- }
- }
- this.block.formatWrap(tag);
- }
- else
- {
- var classSize = 0;
- var toggleType = false;
- if (this.block.type == 'class')
- {
- toggleType = 'toggle';
- classSize = $(this.block.blocks).filter('.' + this.block.value).length;
- if (this.block.blocksSize == classSize) toggleType = 'toggle';
- else if (this.block.blocksSize > classSize) toggleType = 'set';
- else if (classSize === 0) toggleType = 'set';
- }
- var exceptTags = ['ul', 'ol', 'li', 'td', 'th', 'dl', 'dt', 'dd'];
- $.each(this.block.blocks, $.proxy(function(i,s)
- {
- if ($.inArray(s.tagName.toLowerCase(), exceptTags) != -1) return;
- var $formatted = this.utils.replaceToTag(s, tag);
- if (toggleType)
- {
- if (toggleType == 'toggle') this.block.toggle($formatted);
- else if (toggleType == 'remove') this.block.remove($formatted);
- else if (toggleType == 'set') this.block.setForce($formatted);
- }
- else this.block.toggle($formatted);
- if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
- if (this.block.isRemoveInline) this.utils.removeInlineTags($formatted);
- if (tag == 'p' || this.block.headTag) $formatted.find('p').contents().unwrap();
- if (typeof this.block.type == 'undefined' && typeof this.block.value == 'undefined')
- {
- $formatted.removeAttr('class').removeAttr('style');
- }
- }, this));
- }
- }
- },
- setForce: function($el)
- {
- // remove style and class if the specified setting
- if (this.block.clearStyle)
- {
- $el.removeAttr('class').removeAttr('style');
- }
- if (this.block.type == 'class')
- {
- $el.addClass(this.block.value);
- return;
- }
- else if (this.block.type == 'attr' || this.block.type == 'data')
- {
- $el.attr(this.block.value.name, this.block.value.value);
- return;
- }
- },
- toggle: function($el)
- {
- // remove style and class if the specified setting
- if (this.block.clearStyle)
- {
- $el.removeAttr('class').removeAttr('style');
- }
- if (this.block.type == 'class')
- {
- $el.toggleClass(this.block.value);
- return;
- }
- else if (this.block.type == 'attr' || this.block.type == 'data')
- {
- if ($el.attr(this.block.value.name) == this.block.value.value)
- {
- $el.removeAttr(this.block.value.name);
- }
- else
- {
- $el.attr(this.block.value.name, this.block.value.value);
- }
- return;
- }
- else
- {
- $el.removeAttr('style class');
- return;
- }
- },
- remove: function($el)
- {
- $el.removeClass(this.block.value);
- },
- formatListToBlockquote: function()
- {
- var block = $(this.block.blocks[0]).closest('ul, ol', this.$editor[0]);
- $(block).find('ul, ol').contents().unwrap();
- $(block).find('li').append($('<br>')).contents().unwrap();
- var $el = this.utils.replaceToTag(block, 'blockquote');
- this.block.toggle($el);
- },
- formatBlockquote: function(tag)
- {
- document.execCommand('outdent');
- document.execCommand('formatblock', false, tag);
- this.clean.clearUnverified();
- this.$editor.find('p:empty').remove();
- var formatted = this.selection.getBlock();
- if (tag != 'p')
- {
- $(formatted).find('img').remove();
- }
- if (!this.opts.linebreaks)
- {
- this.block.toggle($(formatted));
- }
- this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
- if (this.opts.linebreaks && tag == 'p')
- {
- this.utils.replaceWithContents(formatted);
- }
- },
- formatWrap: function(tag)
- {
- if (this.block.containerTag == 'UL' || this.block.containerTag == 'OL')
- {
- if (tag == 'blockquote')
- {
- this.block.formatListToBlockquote();
- }
- else
- {
- return;
- }
- }
- var formatted = this.selection.wrap(tag);
- if (formatted === false) return;
- var $formatted = $(formatted);
- this.block.formatTableWrapping($formatted);
- var $elements = $formatted.find(this.opts.blockLevelElements.join(',') + ', td, table, thead, tbody, tfoot, th, tr');
- $elements.contents().unwrap();
- if (tag != 'p' && tag != 'blockquote') $formatted.find('img').remove();
- $.each(this.block.blocks, $.proxy(this.utils.removeEmpty, this));
- $formatted.append(this.selection.getMarker(2));
- if (!this.opts.linebreaks)
- {
- this.block.toggle($formatted);
- }
- this.$editor.find('ul, ol, tr, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
- $formatted.find('blockquote:empty').remove();
- if (this.block.isRemoveInline)
- {
- this.utils.removeInlineTags($formatted);
- }
- if (this.opts.linebreaks && tag == 'p')
- {
- this.utils.replaceWithContents($formatted);
- }
- if (this.opts.linebreaks)
- {
- var $next = $formatted.next().next();
- if ($next.size() != 0 && $next[0].tagName === 'BR')
- {
- $next.remove();
- }
- }
- },
- formatTableWrapping: function($formatted)
- {
- if ($formatted.closest('table', this.$editor[0]).length === 0) return;
- if ($formatted.closest('tr', this.$editor[0]).length === 0) $formatted.wrap('<tr>');
- if ($formatted.closest('td', this.$editor[0]).length === 0 && $formatted.closest('th').length === 0)
- {
- $formatted.wrap('<td>');
- }
- },
- removeData: function(name, value)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).removeAttr('data-' + name);
- this.code.sync();
- },
- setData: function(name, value)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).attr('data-' + name, value);
- this.code.sync();
- },
- toggleData: function(name, value)
- {
- var blocks = this.selection.getBlocks();
- $.each(blocks, function()
- {
- if ($(this).attr('data-' + name))
- {
- $(this).removeAttr('data-' + name);
- }
- else
- {
- $(this).attr('data-' + name, value);
- }
- });
- },
- removeAttr: function(attr, value)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).removeAttr(attr);
- this.code.sync();
- },
- setAttr: function(attr, value)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).attr(attr, value);
- this.code.sync();
- },
- toggleAttr: function(attr, value)
- {
- var blocks = this.selection.getBlocks();
- $.each(blocks, function()
- {
- if ($(this).attr(name))
- {
- $(this).removeAttr(name);
- }
- else
- {
- $(this).attr(name, value);
- }
- });
- },
- removeClass: function(className)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).removeClass(className);
- this.utils.removeEmptyAttr(blocks, 'class');
- this.code.sync();
- },
- setClass: function(className)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).addClass(className);
- this.code.sync();
- },
- toggleClass: function(className)
- {
- var blocks = this.selection.getBlocks();
- $(blocks).toggleClass(className);
- this.code.sync();
- }
- };
- },
- buffer: function()
- {
- return {
- set: function(type)
- {
- if (typeof type == 'undefined' || type == 'undo')
- {
- this.buffer.setUndo();
- }
- else
- {
- this.buffer.setRedo();
- }
- },
- setUndo: function()
- {
- this.selection.save();
- this.opts.buffer.push(this.$editor.html());
- this.selection.restore();
- },
- setRedo: function()
- {
- this.selection.save();
- this.opts.rebuffer.push(this.$editor.html());
- this.selection.restore();
- },
- getUndo: function()
- {
- this.$editor.html(this.opts.buffer.pop());
- },
- getRedo: function()
- {
- this.$editor.html(this.opts.rebuffer.pop());
- },
- add: function()
- {
- this.opts.buffer.push(this.$editor.html());
- },
- undo: function()
- {
- if (this.opts.buffer.length === 0) return;
- this.buffer.set('redo');
- this.buffer.getUndo();
- this.selection.restore();
- setTimeout($.proxy(this.observe.load, this), 50);
- },
- redo: function()
- {
- if (this.opts.rebuffer.length === 0) return;
- this.buffer.set('undo');
- this.buffer.getRedo();
- this.selection.restore();
- setTimeout($.proxy(this.observe.load, this), 50);
- }
- };
- },
- build: function()
- {
- return {
- focused: false,
- blured: true,
- run: function()
- {
- this.build.createContainerBox();
- this.build.loadContent();
- this.build.loadEditor();
- this.build.enableEditor();
- this.build.setCodeAndCall();
- },
- isTextarea: function()
- {
- return (this.$element[0].tagName === 'TEXTAREA');
- },
- createContainerBox: function()
- {
- this.$box = $('<div class="redactor-box" role="application" />');
- },
- createTextarea: function()
- {
- this.$textarea = $('<textarea />').attr('name', this.build.getTextareaName());
- },
- getTextareaName: function()
- {
- return ((typeof(name) == 'undefined')) ? 'content-' + this.uuid : this.$element.attr('id');
- },
- loadContent: function()
- {
- var func = (this.build.isTextarea()) ? 'val' : 'html';
- this.content = $.trim(this.$element[func]());
- },
- enableEditor: function()
- {
- this.$editor.attr({ 'contenteditable': true, 'dir': this.opts.direction });
- },
- loadEditor: function()
- {
- var func = (this.build.isTextarea()) ? 'fromTextarea' : 'fromElement';
- this.build[func]();
- },
- fromTextarea: function()
- {
- this.$editor = $('<div />');
- this.$textarea = this.$element;
- this.$box.insertAfter(this.$element).append(this.$editor).append(this.$element);
- this.$editor.addClass('redactor-editor');
- this.$element.hide();
- },
- fromElement: function()
- {
- this.$editor = this.$element;
- this.build.createTextarea();
- this.$box.insertAfter(this.$editor).append(this.$editor).append(this.$textarea);
- this.$editor.addClass('redactor-editor');
- this.$textarea.hide();
- },
- setCodeAndCall: function()
- {
- // set code
- this.code.set(this.content);
- this.build.setOptions();
- this.build.callEditor();
- // code mode
- if (this.opts.visual) return;
- setTimeout($.proxy(this.code.showCode, this), 200);
- },
- callEditor: function()
- {
- this.build.disableMozillaEditing();
- this.build.disableIeLinks();
- this.build.setEvents();
- this.build.setHelpers();
- // load toolbar
- if (this.opts.toolbar)
- {
- this.opts.toolbar = this.toolbar.init();
- this.toolbar.build();
- }
- // modal templates init
- this.modal.loadTemplates();
- // plugins
- this.build.plugins();
- // observers
- setTimeout($.proxy(this.observe.load, this), 4);
- // init callback
- this.core.setCallback('init');
- },
- setOptions: function()
- {
- // textarea direction
- $(this.$textarea).attr('dir', this.opts.direction);
- if (this.opts.linebreaks) this.$editor.addClass('redactor-linebreaks');
- if (this.opts.tabindex) this.$editor.attr('tabindex', this.opts.tabindex);
- if (this.opts.minHeight) this.$editor.css('minHeight', this.opts.minHeight);
- if (this.opts.maxHeight) this.$editor.css('maxHeight', this.opts.maxHeight);
- },
- setEventDropUpload: function(e)
- {
- e.preventDefault();
- if (!this.opts.dragImageUpload || !this.opts.dragFileUpload) return;
- var files = e.dataTransfer.files;
- this.upload.directUpload(files[0], e);
- },
- setEventDrop: function(e)
- {
- this.code.sync();
- setTimeout(this.clean.clearUnverified, 1);
- this.core.setCallback('drop', e);
- },
- setEvents: function()
- {
- // drop
- this.$editor.on('dragover.redactor dragenter.redactor', function(e)
- {
- e.preventDefault();
- e.stopPropagation();
- });
- this.$editor.on('drop.redactor', $.proxy(function(e)
- {
- e = e.originalEvent || e;
- if (window.FormData === undefined || !e.dataTransfer) return true;
- if (e.dataTransfer.files.length === 0)
- {
- return this.build.setEventDrop(e);
- }
- else
- {
- this.build.setEventDropUpload(e);
- }
- setTimeout(this.clean.clearUnverified, 1);
- this.core.setCallback('drop', e);
- }, this));
- // click
- this.$editor.on('click.redactor', $.proxy(function(e)
- {
- var event = this.core.getEvent();
- var type = (event == 'click' || event == 'arrow') ? false : 'click';
- this.core.addEvent(type);
- this.utils.disableSelectAll();
- this.core.setCallback('click', e);
- }, this));
- // paste
- this.$editor.on('paste.redactor', $.proxy(this.paste.init, this));
- // cut
- this.$editor.on('cut.redactor', $.proxy(this.code.sync, this));
- // keydown
- this.$editor.on('keydown.redactor', $.proxy(this.keydown.init, this));
- // keyup
- this.$editor.on('keyup.redactor', $.proxy(this.keyup.init, this));
- // textarea keydown
- if ($.isFunction(this.opts.codeKeydownCallback))
- {
- this.$textarea.on('keydown.redactor-textarea', $.proxy(this.opts.codeKeydownCallback, this));
- }
- // textarea keyup
- if ($.isFunction(this.opts.codeKeyupCallback))
- {
- this.$textarea.on('keyup.redactor-textarea', $.proxy(this.opts.codeKeyupCallback, this));
- }
- // focus
- this.$editor.on('focus.redactor', $.proxy(function(e)
- {
- if ($.isFunction(this.opts.focusCallback))
- {
- this.core.setCallback('focus', e);
- }
- this.build.focused = true;
- this.build.blured = false;
- if (this.selection.getCurrent() === false)
- {
- this.selection.get();
- this.range.setStart(this.$editor[0], 0);
- this.range.setEnd(this.$editor[0], 0);
- this.selection.addRange();
- }
- }, this));
- // blur
- $(document).on('mousedown.redactor-blur.' + this.uuid, $.proxy(function(e)
- {
- if (this.start) return;
- if (this.rtePaste) return;
- if ($(e.target).closest('.redactor-editor, .redactor-toolbar, .redactor-dropdown').size() !== 0)
- {
- return;
- }
- this.utils.disableSelectAll();
- if (!this.build.blured && $.isFunction(this.opts.blurCallback))
- {
- this.core.setCallback('blur', e);
- }
- this.build.focused = false;
- this.build.blured = true;
- }, this));
- },
- setHelpers: function()
- {
- // linkify
- if (this.linkify.isEnabled())
- {
- this.linkify.format();
- }
- // placeholder
- this.placeholder.enable();
- // focus
- if (this.opts.focus) setTimeout(this.focus.setStart, 100);
- if (this.opts.focusEnd) setTimeout(this.focus.setEnd, 100);
- },
- plugins: function()
- {
- if (!this.opts.plugins) return;
- $.each(this.opts.plugins, $.proxy(function(i, s)
- {
- var func = (typeof RedactorPlugins !== 'undefined' && typeof RedactorPlugins[s] !== 'undefined') ? RedactorPlugins : Redactor.fn;
- if (!$.isFunction(func[s]))
- {
- return;
- }
- this[s] = func[s]();
- // get methods
- var methods = this.getModuleMethods(this[s]);
- var len = methods.length;
- // bind methods
- for (var z = 0; z < len; z++)
- {
- this[s][methods[z]] = this[s][methods[z]].bind(this);
- }
- if ($.isFunction(this[s].init))
- {
- this[s].init();
- }
- }, this));
- },
- disableMozillaEditing: function()
- {
- if (!this.utils.browser('mozilla')) return;
- // FF fix
- try {
- document.execCommand('enableObjectResizing', false, false);
- document.execCommand('enableInlineTableEditing', false, false);
- } catch (e) {}
- },
- disableIeLinks: function()
- {
- if (!this.utils.browser('msie')) return;
- // IE prevent converting links
- document.execCommand("AutoUrlDetect", false, false);
- }
- };
- },
- button: function()
- {
- return {
- build: function(btnName, btnObject)
- {
- var $button = $('<a href="#" class="re-icon re-' + btnName + '" rel="' + btnName + '" />').attr({'role': 'button', 'aria-label': btnObject.title, 'tabindex': '-1'});
- // click
- if (btnObject.func || btnObject.command || btnObject.dropdown)
- {
- this.button.setEvent($button, btnName, btnObject);
- }
- // dropdown
- if (btnObject.dropdown)
- {
- $button.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
- var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + btnName + '" style="display: none;">');
- $button.data('dropdown', $dropdown);
- this.dropdown.build(btnName, $dropdown, btnObject.dropdown);
- }
- // tooltip
- if (this.utils.isDesktop())
- {
- this.button.createTooltip($button, btnName, btnObject.title);
- }
- return $button;
- },
- setEvent: function($button, btnName, btnObject)
- {
- $button.on('touchstart click', $.proxy(function(e)
- {
- if ($button.hasClass('redactor-button-disabled')) return false;
- var type = 'func';
- var callback = btnObject.func;
- if (btnObject.command)
- {
- type = 'command';
- callback = btnObject.command;
- }
- else if (btnObject.dropdown)
- {
- type = 'dropdown';
- callback = false;
- }
- this.button.onClick(e, btnName, type, callback);
- }, this));
- },
- createTooltip: function($button, name, title)
- {
- var $tooltip = $('<span>').addClass('redactor-toolbar-tooltip redactor-toolbar-tooltip-' + this.uuid + ' redactor-toolbar-tooltip-' + name).hide().html(title);
- $tooltip.appendTo('body');
- $button.on('mouseover', function()
- {
- if ($(this).hasClass('redactor-button-disabled'))
- {
- return;
- }
- var pos = $button.offset();
- $tooltip.css({
- top: (pos.top + $button.innerHeight()) + 'px',
- left: (pos.left + $button.innerWidth()/2 - $tooltip.innerWidth()/2) + 'px'
- });
- $tooltip.show();
- });
- $button.on('mouseout', function()
- {
- $tooltip.hide();
- });
- },
- onClick: function(e, btnName, type, callback)
- {
- this.button.caretOffset = this.caret.getOffset();
- e.preventDefault();
- $(document).find('.redactor-toolbar-tooltip').hide();
- if (this.utils.browser('msie')) e.returnValue = false;
- if (type == 'command') this.inline.format(callback);
- else if (type == 'dropdown') this.dropdown.show(e, btnName);
- else this.button.onClickCallback(e, callback, btnName);
- },
- onClickCallback: function(e, callback, btnName)
- {
- var func;
- if ($.isFunction(callback)) callback.call(this, btnName);
- else if (callback.search(/\./) != '-1')
- {
- func = callback.split('.');
- if (typeof this[func[0]] == 'undefined') return;
- this[func[0]][func[1]](btnName);
- }
- else this[callback](btnName);
- this.observe.buttons(e, btnName);
- },
- get: function(key)
- {
- return this.$toolbar.find('a.re-' + key);
- },
- setActive: function(key)
- {
- this.button.get(key).addClass('redactor-act');
- },
- setInactive: function(key)
- {
- this.button.get(key).removeClass('redactor-act');
- },
- setInactiveAll: function(key)
- {
- if (typeof key === 'undefined')
- {
- this.$toolbar.find('a.re-icon').removeClass('redactor-act');
- }
- else
- {
- this.$toolbar.find('a.re-icon').not('.re-' + key).removeClass('redactor-act');
- }
- },
- setActiveInVisual: function()
- {
- this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').removeClass('redactor-button-disabled');
- },
- setInactiveInCode: function()
- {
- this.$toolbar.find('a.re-icon').not('a.re-html, a.re-fullscreen').addClass('redactor-button-disabled');
- },
- changeIcon: function(key, classname)
- {
- this.button.get(key).addClass('re-' + classname);
- },
- removeIcon: function(key, classname)
- {
- this.button.get(key).removeClass('re-' + classname);
- },
- setAwesome: function(key, name)
- {
- var $button = this.button.get(key);
- $button.removeClass('redactor-btn-image').addClass('fa-redactor-btn');
- $button.html('<i class="fa ' + name + '"></i>');
- },
- addCallback: function($btn, callback)
- {
- if ($btn == "buffer") return;
- var type = (callback == 'dropdown') ? 'dropdown' : 'func';
- var key = $btn.attr('rel');
- $btn.on('touchstart click', $.proxy(function(e)
- {
- if ($btn.hasClass('redactor-button-disabled')) return false;
- this.button.onClick(e, key, type, callback);
- }, this));
- },
- addDropdown: function($btn, dropdown)
- {
- $btn.addClass('redactor-toolbar-link-dropdown').attr('aria-haspopup', true);
- var key = $btn.attr('rel');
- this.button.addCallback($btn, 'dropdown');
- var $dropdown = $('<div class="redactor-dropdown redactor-dropdown-' + this.uuid + ' redactor-dropdown-box-' + key + '" style="display: none;">');
- $btn.data('dropdown', $dropdown);
- // build dropdown
- if (dropdown) this.dropdown.build(key, $dropdown, dropdown);
- return $dropdown;
- },
- add: function(key, title)
- {
- if (!this.opts.toolbar) return;
- if (this.button.isMobileUndoRedo(key)) return "buffer";
- var btn = this.button.build(key, { title: title });
- btn.addClass('redactor-btn-image');
- this.$toolbar.append($('<li>').append(btn));
- return btn;
- },
- addFirst: function(key, title)
- {
- if (!this.opts.toolbar) return;
- if (this.button.isMobileUndoRedo(key)) return "buffer";
- var btn = this.button.build(key, { title: title });
- btn.addClass('redactor-btn-image');
- this.$toolbar.prepend($('<li>').append(btn));
- return btn;
- },
- addAfter: function(afterkey, key, title)
- {
- if (!this.opts.toolbar) return;
- if (this.button.isMobileUndoRedo(key)) return "buffer";
- var btn = this.button.build(key, { title: title });
- btn.addClass('redactor-btn-image');
- var $btn = this.button.get(afterkey);
- if ($btn.length !== 0) $btn.parent().after($('<li>').append(btn));
- else this.$toolbar.append($('<li>').append(btn));
- return btn;
- },
- addBefore: function(beforekey, key, title)
- {
- if (!this.opts.toolbar) return;
- if (this.button.isMobileUndoRedo(key)) return "buffer";
- var btn = this.button.build(key, { title: title });
- btn.addClass('redactor-btn-image');
- var $btn = this.button.get(beforekey);
- if ($btn.length !== 0) $btn.parent().before($('<li>').append(btn));
- else this.$toolbar.append($('<li>').append(btn));
- return btn;
- },
- remove: function(key)
- {
- this.button.get(key).remove();
- },
- isMobileUndoRedo: function(key)
- {
- return (key == "undo" || key == "redo") && !this.utils.isDesktop();
- }
- };
- },
- caret: function()
- {
- return {
- setStart: function(node)
- {
- // inline tag
- if (!this.utils.isBlock(node))
- {
- var space = this.utils.createSpaceElement();
- $(node).prepend(space);
- this.caret.setEnd(space);
- }
- else
- {
- this.caret.set(node, 0, node, 0);
- }
- },
- setEnd: function(node)
- {
- node = node[0] || node;
- if (node.lastChild.nodeType == 1)
- {
- return this.caret.setAfter(node.lastChild);
- }
- this.caret.set(node, 1, node, 1);
- },
- set: function(orgn, orgo, focn, foco)
- {
- // focus
- // disabled in 10.0.7
- // if (!this.utils.browser('msie')) this.$editor.focus();
- orgn = orgn[0] || orgn;
- focn = focn[0] || focn;
- if (this.utils.isBlockTag(orgn.tagName) && orgn.innerHTML === '')
- {
- orgn.innerHTML = this.opts.invisibleSpace;
- }
- if (orgn.tagName == 'BR' && this.opts.linebreaks === false)
- {
- var parent = $(this.opts.emptyHtml)[0];
- $(orgn).replaceWith(parent);
- orgn = parent;
- focn = orgn;
- }
- this.selection.get();
- try
- {
- this.range.setStart(orgn, orgo);
- this.range.setEnd(focn, foco);
- }
- catch (e) {}
- this.selection.addRange();
- },
- setAfter: function(node)
- {
- try
- {
- var tag = $(node)[0].tagName;
- // inline tag
- if (tag != 'BR' && !this.utils.isBlock(node))
- {
- var space = this.utils.createSpaceElement();
- $(node).after(space);
- this.caret.setEnd(space);
- }
- else
- {
- if (tag != 'BR' && this.utils.browser('msie'))
- {
- this.caret.setStart($(node).next());
- }
- else
- {
- this.caret.setAfterOrBefore(node, 'after');
- }
- }
- }
- catch (e)
- {
- var space = this.utils.createSpaceElement();
- $(node).after(space);
- this.caret.setEnd(space);
- }
- },
- setBefore: function(node)
- {
- // block tag
- if (this.utils.isBlock(node))
- {
- this.caret.setEnd($(node).prev());
- }
- else
- {
- this.caret.setAfterOrBefore(node, 'before');
- }
- },
- setAfterOrBefore: function(node, type)
- {
- // focus
- if (!this.utils.browser('msie')) this.$editor.focus();
- node = node[0] || node;
- this.selection.get();
- if (type == 'after')
- {
- try {
- this.range.setStartAfter(node);
- this.range.setEndAfter(node);
- }
- catch (e) {}
- }
- else
- {
- try {
- this.range.setStartBefore(node);
- this.range.setEndBefore(node);
- }
- catch (e) {}
- }
- this.range.collapse(false);
- this.selection.addRange();
- },
- getOffsetOfElement: function(node)
- {
- node = node[0] || node;
- this.selection.get();
- var cloned = this.range.cloneRange();
- cloned.selectNodeContents(node);
- cloned.setEnd(this.range.endContainer, this.range.endOffset);
- return $.trim(cloned.toString()).length;
- },
- getOffset: function()
- {
- var offset = 0;
- var sel = window.getSelection();
- if (sel.rangeCount > 0)
- {
- var range = window.getSelection().getRangeAt(0);
- var caretRange = range.cloneRange();
- caretRange.selectNodeContents(this.$editor[0]);
- caretRange.setEnd(range.endContainer, range.endOffset);
- offset = caretRange.toString().length;
- }
- return offset;
- },
- setOffset: function(start, end)
- {
- if (typeof end == 'undefined') end = start;
- if (!this.focus.isFocused()) this.focus.setStart();
- var sel = this.selection.get();
- var node, offset = 0;
- var walker = document.createTreeWalker(this.$editor[0], NodeFilter.SHOW_TEXT, null, null);
- while (node = walker.nextNode())
- {
- offset += node.nodeValue.length;
- if (offset > start)
- {
- this.range.setStart(node, node.nodeValue.length + start - offset);
- start = Infinity;
- }
- if (offset >= end)
- {
- this.range.setEnd(node, node.nodeValue.length + end - offset);
- break;
- }
- }
- this.range.collapse(false);
- this.selection.addRange();
- },
- // deprecated
- setToPoint: function(start, end)
- {
- this.caret.setOffset(start, end);
- },
- getCoords: function()
- {
- return this.caret.getOffset();
- }
- };
- },
- clean: function()
- {
- return {
- onSet: function(html)
- {
- html = this.clean.savePreCode(html);
- // convert script tag
- html = html.replace(/<script(.*?[^>]?)>([\w\W]*?)<\/script>/gi, '<pre class="redactor-script-tag" style="display: none;" $1>$2</pre>');
- // replace dollar sign to entity
- html = html.replace(/\$/g, '$');
- // replace special characters in links
- html = html.replace(/<a href="(.*?[^>]?)®(.*?[^>]?)">/gi, '<a href="$1®$2">');
- if (this.opts.replaceDivs && !this.opts.linebreaks) html = this.clean.replaceDivs(html);
- if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
- // save form tag
- html = this.clean.saveFormTags(html);
- // convert font tag to span
- var $div = $('<div>');
- $div.html(html);
- var fonts = $div.find('font[style]');
- if (fonts.length !== 0)
- {
- fonts.replaceWith(function()
- {
- var $el = $(this);
- var $span = $('<span>').attr('style', $el.attr('style'));
- return $span.append($el.contents());
- });
- html = $div.html();
- }
- $div.remove();
- // remove font tag
- html = html.replace(/<font(.*?)>/gi, '');
- html = html.replace(/<\/font>/gi, '');
- // tidy html
- html = this.tidy.load(html);
- // paragraphize
- if (this.opts.paragraphize) html = this.paragraphize.load(html);
- // verified
- html = this.clean.setVerified(html);
- // convert inline tags
- html = this.clean.convertInline(html);
- html = html.replace(/&/g, '&');
- return html;
- },
- onSync: function(html)
- {
- // remove spaces
- html = html.replace(/\u200B/g, '');
- html = html.replace(/​/gi, '');
- if (this.opts.cleanSpaces)
- {
- html = html.replace(/ /gi, ' ');
- }
- if (html.search(/^<p>(||\s||<br\s?\/?>|| )<\/p>$/i) != -1)
- {
- return '';
- }
- // reconvert script tag
- html = html.replace(/<pre class="redactor-script-tag" style="display: none;"(.*?[^>]?)>([\w\W]*?)<\/pre>/gi, '<script$1>$2</script>');
- // restore form tag
- html = this.clean.restoreFormTags(html);
- var chars = {
- '\u2122': '™',
- '\u00a9': '©',
- '\u2026': '…',
- '\u2014': '—',
- '\u2010': '‐'
- };
- // replace special characters
- $.each(chars, function(i,s)
- {
- html = html.replace(new RegExp(i, 'g'), s);
- });
- // remove last br in FF
- if (this.utils.browser('mozilla'))
- {
- html = html.replace(/<br\s?\/?>$/gi, '');
- }
- // remove br in|of li tags
- html = html.replace(new RegExp('<br\\s?/?></li>', 'gi'), '</li>');
- html = html.replace(new RegExp('</li><br\\s?/?>', 'gi'), '</li>');
- // remove empty attributes
- html = html.replace(/<(.*?)rel="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
- html = html.replace(/<(.*?)style="\s*?"(.*?[^>]?)>/gi, '<$1$2">');
- html = html.replace(/="">/gi, '>');
- html = html.replace(/""">/gi, '">');
- html = html.replace(/"">/gi, '">');
- // remove verified
- html = html.replace(/<div(.*?)data-tagblock="redactor"(.*?[^>])>/gi, '<div$1$2>');
- html = html.replace(/<(.*?) data-verified="redactor"(.*?[^>])>/gi, '<$1$2>');
- var $div = $("<div/>").html($.parseHTML(html, document, true));
- $div.find("span").removeAttr("rel");
- $div.find('pre .redactor-invisible-space').each(function()
- {
- $(this).contents().unwrap();
- });
- html = $div.html();
- // remove rel attribute from img
- html = html.replace(/<img(.*?[^>])rel="(.*?[^>])"(.*?[^>])>/gi, '<img$1$3>');
- html = html.replace(/<span class="redactor-invisible-space">(.*?)<\/span>/gi, '$1');
- html = html.replace(/ data-save-url="(.*?[^>])"/gi, '');
- // remove image resize
- html = html.replace(/<span(.*?)id="redactor-image-box"(.*?[^>])>([\w\W]*?)<img(.*?)><\/span>/gi, '$3<img$4>');
- html = html.replace(/<span(.*?)id="redactor-image-resizer"(.*?[^>])>(.*?)<\/span>/gi, '');
- html = html.replace(/<span(.*?)id="redactor-image-editter"(.*?[^>])>(.*?)<\/span>/gi, '');
- // remove font tag
- html = html.replace(/<font(.*?)>/gi, '');
- html = html.replace(/<\/font>/gi, '');
- // tidy html
- html = this.tidy.load(html);
- // link nofollow
- if (this.opts.linkNofollow)
- {
- html = html.replace(/<a(.*?)rel="nofollow"(.*?[^>])>/gi, '<a$1$2>');
- html = html.replace(/<a(.*?[^>])>/gi, '<a$1 rel="nofollow">');
- }
- // reconvert inline
- html = html.replace(/\sdata-redactor-(tag|class|style)="(.*?[^>])"/gi, '');
- html = html.replace(new RegExp('<(.*?) data-verified="redactor"(.*?[^>])>', 'gi'), '<$1$2>');
- html = html.replace(new RegExp('<(.*?) data-verified="redactor">', 'gi'), '<$1>');
- html = html.replace(/&/g, '&');
- return html;
- },
- onPaste: function(html, setMode)
- {
- html = $.trim(html);
- html = html.replace(/\$/g, '$');
- // convert dirty spaces
- html = html.replace(/<span class="s[0-9]">/gi, '<span>');
- html = html.replace(/<span class="Apple-converted-space"> <\/span>/gi, ' ');
- html = html.replace(/<span class="Apple-tab-span"[^>]*>\t<\/span>/gi, '\t');
- html = html.replace(/<span[^>]*>(\s| )<\/span>/gi, ' ');
- if (this.opts.pastePlainText)
- {
- return this.clean.getPlainText(html);
- }
- if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
- {
- if (this.utils.isCurrentOrParent(['FIGCAPTION', 'A']))
- {
- return this.clean.getPlainText(html, false);
- }
- if (this.utils.isCurrentOrParent('PRE'))
- {
- html = html.replace(/”/g, '"');
- html = html.replace(/“/g, '"');
- html = html.replace(/‘/g, '\'');
- html = html.replace(/’/g, '\'');
- return this.clean.getPreCode(html);
- }
- if (this.utils.isCurrentOrParent(['BLOCKQUOTE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']))
- {
- html = this.clean.getOnlyImages(html);
- if (!this.utils.browser('msie'))
- {
- var block = this.selection.getBlock();
- if (block && block.tagName == 'P')
- {
- html = html.replace(/<img(.*?)>/gi, '<p><img$1></p>');
- }
- }
- return html;
- }
- if (this.utils.isCurrentOrParent(['TD']))
- {
- html = this.clean.onPasteTidy(html, 'td');
- if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
- html = this.clean.replaceDivsToBr(html);
- return html;
- }
- if (this.utils.isCurrentOrParent(['LI']))
- {
- return this.clean.onPasteTidy(html, 'li');
- }
- }
- html = this.clean.isSingleLine(html, setMode);
- if (!this.clean.singleLine)
- {
- if (this.opts.linebreaks) html = this.clean.replaceParagraphsToBr(html);
- if (this.opts.replaceDivs) html = this.clean.replaceDivs(html);
- html = this.clean.saveFormTags(html);
- }
- html = this.clean.onPasteWord(html);
- html = this.clean.onPasteExtra(html);
- html = this.clean.onPasteTidy(html, 'all');
- // paragraphize
- if (!this.clean.singleLine && this.opts.paragraphize)
- {
- html = this.paragraphize.load(html);
- }
- html = this.clean.removeDirtyStyles(html);
- html = this.clean.onPasteRemoveSpans(html);
- html = this.clean.onPasteRemoveEmpty(html);
- html = this.clean.convertInline(html);
- return html;
- },
- onPasteWord: function(html)
- {
- // comments
- html = html.replace(/<!--[\s\S]*?-->/gi, '');
- // style
- html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
- // op
- html = html.replace(/<o\:p[^>]*>[\s\S]*?<\/o\:p>/gi, '');
- if (html.match(/class="?Mso|style="[^"]*\bmso-|style='[^'']*\bmso-|w:WordDocument/i))
- {
- // comments
- html = html.replace(/<!--[\s\S]+?-->/gi, '');
- // scripts
- html = html.replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi, '');
- // Convert <s> into <strike>
- html = html.replace(/<(\/?)s>/gi, "<$1strike>");
- // Replace nbsp entites to char since it's easier to handle
- html = html.replace(/ /gi, ' ');
- // Convert <span style="mso-spacerun:yes">___</span> to string of alternating
- // breaking/non-breaking spaces of same length
- html = html.replace(/<span\s+style\s*=\s*"\s*mso-spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi, function(str, spaces) {
- return (spaces.length > 0) ? spaces.replace(/./, " ").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : '';
- });
- html = this.clean.onPasteIeFixLinks(html);
- // shapes
- html = html.replace(/<img(.*?)v:shapes=(.*?)>/gi, '');
- html = html.replace(/src="file\:\/\/(.*?)"/, 'src=""');
- // lists
- var $div = $("<div/>").html(html);
- var lastList = false;
- var lastLevel = 1;
- var listsIds = [];
- $div.find("p[style]").each(function()
- {
- var matches = $(this).attr('style').match(/mso\-list\:l([0-9]+)\slevel([0-9]+)/);
- if (matches)
- {
- var currentList = parseInt(matches[1]);
- var currentLevel = parseInt(matches[2]);
- var listType = $(this).html().match(/^[\w]+\./) ? "ol" : "ul";
- var $li = $("<li/>").html($(this).html());
- $li.html($li.html().replace(/^([\w\.]+)</, '<'));
- $li.find("span:first").remove();
- if (currentLevel == 1 && $.inArray(currentList, listsIds) == -1)
- {
- var $list = $("<" + listType + "/>").attr({"data-level": currentLevel,
- "data-list": currentList})
- .html($li);
- $(this).replaceWith($list);
- lastList = currentList;
- listsIds.push(currentList);
- }
- else
- {
- if (currentLevel > lastLevel)
- {
- var $prevList = $div.find('[data-level="' + lastLevel + '"][data-list="' + lastList + '"]');
- var $lastList = $prevList;
- for(var i = lastLevel; i < currentLevel; i++)
- {
- $list = $("<" + listType + "/>");
- $list.appendTo($lastList.find("li").last());
- $lastList = $list;
- }
- $lastList.attr({"data-level": currentLevel,
- "data-list": currentList})
- .html($li);
- }
- else
- {
- var $prevList = $div.find('[data-level="' + currentLevel + '"][data-list="' + currentList + '"]').last();
- $prevList.append($li);
- }
- lastLevel = currentLevel;
- lastList = currentList;
- $(this).remove();
- }
- }
- });
- $div.find('[data-level][data-list]').removeAttr('data-level data-list');
- html = $div.html();
- // remove ms word's bullet
- html = html.replace(/·/g, '');
- html = html.replace(/<p class="Mso(.*?)"/gi, '<p');
- // classes
- html = html.replace(/ class=\"(mso[^\"]*)\"/gi, "");
- html = html.replace(/ class=(mso\w+)/gi, "");
- // remove ms word tags
- html = html.replace(/<o:p(.*?)>([\w\W]*?)<\/o:p>/gi, '$2');
- // ms word break lines
- html = html.replace(/\n/g, ' ');
- // ms word lists break lines
- html = html.replace(/<p>\n?<li>/gi, '<li>');
- }
- return html;
- },
- onPasteExtra: function(html)
- {
- // remove google docs markers
- html = html.replace(/<b\sid="internal-source-marker(.*?)">([\w\W]*?)<\/b>/gi, "$2");
- html = html.replace(/<b(.*?)id="docs-internal-guid(.*?)">([\w\W]*?)<\/b>/gi, "$3");
- // google docs styles
- html = html.replace(/<span[^>]*(font-style: italic; font-weight: bold|font-weight: bold; font-style: italic)[^>]*>/gi, '<span style="font-weight: bold;"><span style="font-style: italic;">');
- html = html.replace(/<span[^>]*font-style: italic[^>]*>/gi, '<span style="font-style: italic;">');
- html = html.replace(/<span[^>]*font-weight: bold[^>]*>/gi, '<span style="font-weight: bold;">');
- html = html.replace(/<span[^>]*text-decoration: underline[^>]*>/gi, '<span style="text-decoration: underline;">');
- html = html.replace(/<img>/gi, '');
- html = html.replace(/\n{3,}/gi, '\n');
- html = html.replace(/<font(.*?)>([\w\W]*?)<\/font>/gi, '$2');
- // remove dirty p
- html = html.replace(/<p><p>/gi, '<p>');
- html = html.replace(/<\/p><\/p>/gi, '</p>');
- html = html.replace(/<li>(\s*|\t*|\n*)<p>/gi, '<li>');
- html = html.replace(/<\/p>(\s*|\t*|\n*)<\/li>/gi, '</li>');
- // remove space between paragraphs
- html = html.replace(/<\/p>\s<p/gi, '<\/p><p');
- // remove safari local images
- html = html.replace(/<img src="webkit-fake-url\:\/\/(.*?)"(.*?)>/gi, '');
- // bullets
- html = html.replace(/<p>•([\w\W]*?)<\/p>/gi, '<li>$1</li>');
- // FF fix
- if (this.utils.browser('mozilla'))
- {
- html = html.replace(/<br\s?\/?>$/gi, '');
- }
- return html;
- },
- onPasteTidy: function(html, type)
- {
- // remove all tags except these
- var tags = ['span', 'a', 'pre', 'blockquote', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'address', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
- 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'p', 'br', 'video', 'audio', 'iframe', 'embed', 'param', 'object', 'img', 'table',
- 'td', 'th', 'tr', 'tbody', 'tfoot', 'thead', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
- var tagsEmpty = false;
- var attrAllowed = [
- ['a', '*'],
- ['img', ['src', 'alt']],
- ['span', ['class', 'rel', 'data-verified']],
- ['iframe', '*'],
- ['video', '*'],
- ['audio', '*'],
- ['embed', '*'],
- ['object', '*'],
- ['param', '*'],
- ['source', '*']
- ];
- if (type == 'all')
- {
- tagsEmpty = ['p', 'span', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
- attrAllowed = [
- ['table', 'class'],
- ['td', ['colspan', 'rowspan']],
- ['a', '*'],
- ['img', ['src', 'alt', 'data-redactor-inserted-image']],
- ['span', ['class', 'rel', 'data-verified']],
- ['iframe', '*'],
- ['video', '*'],
- ['audio', '*'],
- ['embed', '*'],
- ['object', '*'],
- ['param', '*'],
- ['source', '*']
- ];
- }
- else if (type == 'td')
- {
- // remove all tags except these and remove all table tags: tr, td etc
- tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del',
- 'ol', 'ul', 'li', 'dl', 'dt', 'dd', 'br', 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
- }
- else if (type == 'li')
- {
- // only inline tags and ul, ol, li
- tags = ['ul', 'ol', 'li', 'span', 'a', 'small', 'em', 'strong', 'code', 'kbd', 'mark', 'cite', 'var', 'samp', 'dfn', 'sup', 'sub', 'b', 'i', 'u', 'del', 'br',
- 'iframe', 'video', 'audio', 'embed', 'param', 'object', 'img'];
- }
- var options = {
- deniedTags: (this.opts.deniedTags) ? this.opts.deniedTags : false,
- allowedTags: (this.opts.allowedTags) ? this.opts.allowedTags : tags,
- removeComments: true,
- removePhp: true,
- removeAttr: (this.opts.removeAttr) ? this.opts.removeAttr : false,
- allowedAttr: (this.opts.allowedAttr) ? this.opts.allowedAttr : attrAllowed,
- removeEmpty: tagsEmpty
- };
- return this.tidy.load(html, options);
- },
- onPasteRemoveEmpty: function(html)
- {
- html = html.replace(/<(p|h[1-6])>(|\s|\n|\t|<br\s?\/?>)<\/(p|h[1-6])>/gi, '');
- // remove br in the end
- if (!this.opts.linebreaks) html = html.replace(/<br>$/i, '');
- return html;
- },
- onPasteRemoveSpans: function(html)
- {
- html = html.replace(/<span>(.*?)<\/span>/gi, '$1');
- html = html.replace(/<span[^>]*>\s| <\/span>/gi, ' ');
- return html;
- },
- onPasteIeFixLinks: function(html)
- {
- if (!this.utils.browser('msie')) return html;
- var tmp = $.trim(html);
- if (tmp.search(/^<a(.*?)>(.*?)<\/a>$/i) === 0)
- {
- html = html.replace(/^<a(.*?)>(.*?)<\/a>$/i, "$2");
- }
- return html;
- },
- isSingleLine: function(html, setMode)
- {
- this.clean.singleLine = false;
- if (!this.utils.isSelectAll() && typeof setMode == 'undefined')
- {
- var blocks = this.opts.blockLevelElements.join('|').replace('P|', '').replace('DIV|', '');
- var matchBlocks = html.match(new RegExp('</(' + blocks + ')>', 'gi'));
- var matchContainers = html.match(/<\/(p|div)>/gi);
- if (!matchBlocks && (matchContainers === null || (matchContainers && matchContainers.length <= 1)))
- {
- var matchBR = html.match(/<br\s?\/?>/gi);
- //var matchIMG = html.match(/<img(.*?[^>])>/gi);
- if (!matchBR)
- {
- this.clean.singleLine = true;
- html = html.replace(/<\/?(p|div)(.*?)>/gi, '');
- }
- }
- }
- return html;
- },
- stripTags: function(input, allowed)
- {
- allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
- var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
- return input.replace(tags, function ($0, $1) {
- return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
- });
- },
- savePreCode: function(html)
- {
- html = this.clean.savePreFormatting(html);
- html = this.clean.saveCodeFormatting(html);
- html = this.clean.restoreSelectionMarker(html);
- return html;
- },
- savePreFormatting: function(html)
- {
- var pre = html.match(/<pre(.*?)>([\w\W]*?)<\/pre>/gi);
- if (pre !== null)
- {
- $.each(pre, $.proxy(function(i,s)
- {
- var arr = s.match(/<pre(.*?)>([\w\W]*?)<\/pre>/i);
- arr[2] = arr[2].replace(/<br\s?\/?>/g, '\n');
- arr[2] = arr[2].replace(/ /g, ' ');
- if (this.opts.preSpaces)
- {
- arr[2] = arr[2].replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
- }
- arr[2] = this.clean.encodeEntities(arr[2]);
- // $ fix
- arr[2] = arr[2].replace(/\$/g, '$');
- html = html.replace(s, '<pre' + arr[1] + '>' + arr[2] + '</pre>');
- }, this));
- }
- return html;
- },
- saveCodeFormatting: function(html)
- {
- var code = html.match(/<code(.*?)>([\w\W]*?)<\/code>/gi);
- if (code !== null)
- {
- $.each(code, $.proxy(function(i,s)
- {
- var arr = s.match(/<code(.*?)>([\w\W]*?)<\/code>/i);
- arr[2] = arr[2].replace(/ /g, ' ');
- arr[2] = this.clean.encodeEntities(arr[2]);
- arr[2] = arr[2].replace(/\$/g, '$');
- html = html.replace(s, '<code' + arr[1] + '>' + arr[2] + '</code>');
- }, this));
- }
- return html;
- },
- restoreSelectionMarker: function(html)
- {
- html = html.replace(/<span id="selection-marker-([0-9])" class="redactor-selection-marker" data-verified="redactor"><\/span>/g, '<span id="selection-marker-$1" class="redactor-selection-marker" data-verified="redactor"></span>');
- return html;
- },
- getTextFromHtml: function(html)
- {
- html = html.replace(/<br\s?\/?>|<\/H[1-6]>|<\/p>|<\/div>|<\/li>|<\/td>/gi, '\n');
- var tmp = document.createElement('div');
- tmp.innerHTML = html;
- html = tmp.textContent || tmp.innerText;
- return $.trim(html);
- },
- getPlainText: function(html, paragraphize)
- {
- html = this.clean.getTextFromHtml(html);
- html = html.replace(/\n\s*\n/g, "\n");
- html = html.replace(/\n\n/g, "\n");
- html = html.replace(/\n/g, '<br />');
- if (this.opts.paragraphize && typeof paragraphize == 'undefined' && !this.utils.browser('mozilla'))
- {
- html = this.paragraphize.load(html);
- }
- return html;
- },
- getPreCode: function(html)
- {
- html = html.replace(/<img(.*?) style="(.*?)"(.*?[^>])>/gi, '<img$1$3>');
- html = html.replace(/<img(.*?)>/gi, '<img$1>');
- html = this.clean.getTextFromHtml(html);
- if (this.opts.preSpaces)
- {
- html = html.replace(/\t/g, Array(this.opts.preSpaces + 1).join(' '));
- }
- html = this.clean.encodeEntities(html);
- return html;
- },
- getOnlyImages: function(html)
- {
- html = html.replace(/<img(.*?)>/gi, '[img$1]');
- // remove all tags
- html = html.replace(/<([Ss]*?)>/gi, '');
- html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
- return html;
- },
- getOnlyLinksAndImages: function(html)
- {
- html = html.replace(/<a(.*?)href="(.*?)"(.*?)>([\w\W]*?)<\/a>/gi, '[a href="$2"]$4[/a]');
- html = html.replace(/<img(.*?)>/gi, '[img$1]');
- // remove all tags
- html = html.replace(/<(.*?)>/gi, '');
- html = html.replace(/\[a href="(.*?)"\]([\w\W]*?)\[\/a\]/gi, '<a href="$1">$2</a>');
- html = html.replace(/\[img(.*?)\]/gi, '<img$1>');
- return html;
- },
- encodeEntities: function(str)
- {
- str = String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
- return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
- },
- removeDirtyStyles: function(html)
- {
- if (this.utils.browser('msie')) return html;
- var div = document.createElement('div');
- div.innerHTML = html;
- this.clean.clearUnverifiedRemove($(div));
- html = div.innerHTML;
- $(div).remove();
- return html;
- },
- clearUnverified: function()
- {
- if (this.utils.browser('msie')) return;
- this.clean.clearUnverifiedRemove(this.$editor);
- var headers = this.$editor.find('h1, h2, h3, h4, h5, h6');
- headers.find('span').removeAttr('style');
- headers.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
- this.code.sync();
- },
- clearUnverifiedRemove: function($editor)
- {
- $editor.find(this.opts.verifiedTags.join(', ')).removeAttr('style');
- $editor.find('span').not('[data-verified="redactor"]').removeAttr('style');
- $editor.find('span[data-verified="redactor"], img[data-verified="redactor"]').each(function(i, s)
- {
- var $s = $(s);
- $s.attr('style', $s.attr('rel'));
- });
- },
- cleanEmptyParagraph: function()
- {
- },
- setVerified: function(html)
- {
- if (this.utils.browser('msie')) return html;
- html = html.replace(new RegExp('<img(.*?[^>])>', 'gi'), '<img$1 data-verified="redactor">');
- html = html.replace(new RegExp('<span(.*?[^>])>', 'gi'), '<span$1 data-verified="redactor">');
- var matches = html.match(new RegExp('<(span|img)(.*?)style="(.*?)"(.*?[^>])>', 'gi'));
- if (matches)
- {
- var len = matches.length;
- for (var i = 0; i < len; i++)
- {
- try {
- var newTag = matches[i].replace(/style="(.*?)"/i, 'style="$1" rel="$1"');
- html = html.replace(matches[i], newTag);
- }
- catch (e) {}
- }
- }
- return html;
- },
- convertInline: function(html)
- {
- var $div = $('<div />').html(html);
- var tags = this.opts.inlineTags;
- tags.push('span');
- $div.find(tags.join(',')).each(function()
- {
- var $el = $(this);
- var tag = this.tagName.toLowerCase();
- $el.attr('data-redactor-tag', tag);
- if (tag == 'span')
- {
- if ($el.attr('style')) $el.attr('data-redactor-style', $el.attr('style'));
- else if ($el.attr('class')) $el.attr('data-redactor-class', $el.attr('class'));
- }
- });
- html = $div.html();
- $div.remove();
- return html;
- },
- normalizeLists: function()
- {
- this.$editor.find('li').each(function(i,s)
- {
- var $next = $(s).next();
- if ($next.length !== 0 && ($next[0].tagName == 'UL' || $next[0].tagName == 'OL'))
- {
- $(s).append($next);
- }
- });
- },
- removeSpaces: function(html)
- {
- html = html.replace(/\n/g, '');
- html = html.replace(/[\t]*/g, '');
- html = html.replace(/\n\s*\n/g, "\n");
- html = html.replace(/^[\s\n]*/g, ' ');
- html = html.replace(/[\s\n]*$/g, ' ');
- html = html.replace( />\s{2,}</g, '> <'); // between inline tags can be only one space
- html = html.replace(/\n\n/g, "\n");
- html = html.replace(/\u200B/g, '');
- return html;
- },
- replaceDivs: function(html)
- {
- if (this.opts.linebreaks)
- {
- html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br />');
- html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '$2<br />');
- }
- else
- {
- html = html.replace(/<div(.*?)>([\w\W]*?)<\/div>/gi, '<p$1>$2</p>');
- }
- html = html.replace(/<div(.*?[^>])>/gi, '');
- html = html.replace(/<\/div>/gi, '');
- return html;
- },
- replaceDivsToBr: function(html)
- {
- html = html.replace(/<div\s(.*?)>/gi, '<p>');
- html = html.replace(/<div><br\s?\/?><\/div>/gi, '<br /><br />');
- html = html.replace(/<div>([\w\W]*?)<\/div>/gi, '$1<br /><br />');
- return html;
- },
- replaceParagraphsToBr: function(html)
- {
- html = html.replace(/<p\s(.*?)>/gi, '<p>');
- html = html.replace(/<p><br\s?\/?><\/p>/gi, '<br />');
- html = html.replace(/<p>([\w\W]*?)<\/p>/gi, '$1<br /><br />');
- html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
- return html;
- },
- saveFormTags: function(html)
- {
- return html.replace(/<form(.*?)>([\w\W]*?)<\/form>/gi, '<section$1 rel="redactor-form-tag">$2</section>');
- },
- restoreFormTags: function(html)
- {
- return html.replace(/<section(.*?) rel="redactor-form-tag"(.*?)>([\w\W]*?)<\/section>/gi, '<form$1$2>$3</form>');
- }
- };
- },
- code: function()
- {
- return {
- set: function(html)
- {
- html = $.trim(html.toString());
- // clean
- html = this.clean.onSet(html);
- if (this.utils.browser('msie'))
- {
- html = html.replace(/<span(.*?)id="selection-marker-(1|2)"(.*?)><\/span>/gi, '');
- }
- this.$editor.html(html);
- this.code.sync();
- if (html !== '') this.placeholder.remove();
- setTimeout($.proxy(this.buffer.add, this), 15);
- if (this.start === false) this.observe.load();
- },
- get: function()
- {
- var code = this.$textarea.val();
- if (this.opts.replaceDivs) code = this.clean.replaceDivs(code);
- if (this.opts.linebreaks) code = this.clean.replaceParagraphsToBr(code);
- // indent code
- code = this.tabifier.get(code);
- return code;
- },
- sync: function()
- {
- setTimeout($.proxy(this.code.startSync, this), 10);
- },
- startSync: function()
- {
- var html = this.$editor.html();
- // is there a need to synchronize
- if (this.code.syncCode && this.code.syncCode == html || (this.start && html == '' ))
- {
- // do not sync
- return;
- }
- // save code
- this.code.syncCode = html;
- // before clean callback
- html = this.core.setCallback('syncBefore', html);
- // clean
- html = this.clean.onSync(html);
- // set code
- this.$textarea.val(html);
- // after sync callback
- this.core.setCallback('sync', html);
- if (this.start === false)
- {
- this.core.setCallback('change', html);
- }
- this.start = false;
- if (this.autosave.html == false)
- {
- this.autosave.html = this.code.get();
- }
- if (this.opts.codemirror)
- {
- this.$textarea.next('.CodeMirror').each(function(i, el)
- {
- el.CodeMirror.setValue(html);
- });
- }
- //autosave
- this.autosave.onChange();
- this.autosave.enable();
- },
- toggle: function()
- {
- if (this.opts.visual)
- {
- this.code.showCode();
- }
- else
- {
- this.code.showVisual();
- }
- },
- showCode: function()
- {
- this.selection.save();
- this.code.offset = this.caret.getOffset();
- var scroll = $(window).scrollTop();
- var width = this.$editor.innerWidth(),
- height = this.$editor.innerHeight();
- this.$editor.hide();
- var html = this.$textarea.val();
- this.modified = this.clean.removeSpaces(html);
- // indent code
- html = this.tabifier.get(html);
- // caret position sync
- var start = 0, end = 0;
- var $editorDiv = $("<div/>").append($.parseHTML(this.clean.onSync(this.$editor.html()), document, true));
- var $selectionMarkers = $editorDiv.find("span.redactor-selection-marker");
- if ($selectionMarkers.length > 0)
- {
- var editorHtml = this.tabifier.get($editorDiv.html()).replace(/&/g, '&');
- if ($selectionMarkers.length == 1)
- {
- start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
- end = start;
- }
- else if ($selectionMarkers.length == 2)
- {
- start = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-1").prop("outerHTML"));
- end = this.utils.strpos(editorHtml, $editorDiv.find("#selection-marker-2").prop("outerHTML")) - $editorDiv.find("#selection-marker-1").prop("outerHTML").toString().length;
- }
- }
- this.selection.removeMarkers();
- this.$textarea.val(html);
- if (this.opts.codemirror)
- {
- this.$textarea.next('.CodeMirror').each(function(i, el)
- {
- $(el).show();
- el.CodeMirror.setValue(html);
- el.CodeMirror.setSize('100%', height);
- el.CodeMirror.refresh();
- if (start == end)
- {
- el.CodeMirror.setCursor(el.CodeMirror.posFromIndex(start).line, el.CodeMirror.posFromIndex(end).ch);
- }
- else
- {
- el.CodeMirror.setSelection({line: el.CodeMirror.posFromIndex(start).line,
- ch: el.CodeMirror.posFromIndex(start).ch},
- {line: el.CodeMirror.posFromIndex(end).line,
- ch: el.CodeMirror.posFromIndex(end).ch});
- }
- el.CodeMirror.focus();
- });
- }
- else
- {
- this.$textarea.height(height).show().focus();
- this.$textarea.on('keydown.redactor-textarea-indenting', this.code.textareaIndenting);
- $(window).scrollTop(scroll);
- if (this.$textarea[0].setSelectionRange)
- {
- this.$textarea[0].setSelectionRange(start, end);
- }
- this.$textarea[0].scrollTop = 0;
- }
- this.opts.visual = false;
- this.button.setInactiveInCode();
- this.button.setActive('html');
- this.core.setCallback('source', html);
- },
- showVisual: function()
- {
- var html;
- if (this.opts.visual) return;
- var start = 0, end = 0;
- if (this.opts.codemirror)
- {
- var selection;
- this.$textarea.next('.CodeMirror').each(function(i, el)
- {
- selection = el.CodeMirror.listSelections();
- start = el.CodeMirror.indexFromPos(selection[0].anchor);
- end = el.CodeMirror.indexFromPos(selection[0].head);
- html = el.CodeMirror.getValue();
- });
- }
- else
- {
- start = this.$textarea.get(0).selectionStart;
- end = this.$textarea.get(0).selectionEnd;
- html = this.$textarea.hide().val();
- }
- // if selection starts from end
- if (start > end && end > 0)
- {
- var tempStart = end;
- var tempEnd = start;
- start = tempStart;
- end = tempEnd;
- }
- start = this.code.enlargeOffset(html, start);
- end = this.code.enlargeOffset(html, end);
- html = html.substr(0, start) + this.selection.getMarkerAsHtml(1) + html.substr(start);
- if (end > start)
- {
- var markerLength = this.selection.getMarkerAsHtml(1).toString().length;
- html = html.substr(0, end + markerLength) + this.selection.getMarkerAsHtml(2) + html.substr(end + markerLength);
- }
- if (this.modified !== this.clean.removeSpaces(html))
- {
- this.code.set(html);
- }
- if (this.opts.codemirror)
- {
- this.$textarea.next('.CodeMirror').hide();
- }
- this.$editor.show();
- if (!this.utils.isEmpty(html))
- {
- this.placeholder.remove();
- }
- this.selection.restore();
- this.$textarea.off('keydown.redactor-textarea-indenting');
- this.button.setActiveInVisual();
- this.button.setInactive('html');
- this.observe.load();
- this.opts.visual = true;
- this.core.setCallback('visual', html);
- },
- textareaIndenting: function(e)
- {
- if (e.keyCode !== 9) return true;
- var $el = this.$textarea;
- var start = $el.get(0).selectionStart;
- $el.val($el.val().substring(0, start) + "\t" + $el.val().substring($el.get(0).selectionEnd));
- $el.get(0).selectionStart = $el.get(0).selectionEnd = start + 1;
- return false;
- },
- enlargeOffset: function(html, offset)
- {
- var htmlLength = html.length;
- var c = 0;
- if (html[offset] == '>')
- {
- c++;
- }
- else
- {
- for(var i = offset; i <= htmlLength; i++)
- {
- c++;
- if (html[i] == '>')
- {
- break;
- }
- else if (html[i] == '<' || i == htmlLength)
- {
- c = 0;
- break;
- }
- }
- }
- return offset + c;
- }
- };
- },
- core: function()
- {
- return {
- getObject: function()
- {
- return $.extend({}, this);
- },
- getEditor: function()
- {
- return this.$editor;
- },
- getBox: function()
- {
- return this.$box;
- },
- getElement: function()
- {
- return this.$element;
- },
- getTextarea: function()
- {
- return this.$textarea;
- },
- getToolbar: function()
- {
- return (this.$toolbar) ? this.$toolbar : false;
- },
- addEvent: function(name)
- {
- this.core.event = name;
- },
- getEvent: function()
- {
- return this.core.event;
- },
- setCallback: function(type, e, data)
- {
- var eventName = type + 'Callback';
- var eventNamespace = 'redactor';
- var callback = this.opts[eventName];
- if (this.$textarea)
- {
- var returnValue = false;
- var events = $._data(this.$textarea[0], 'events');
- if (typeof events != 'undefined' && typeof events[eventName] != 'undefined')
- {
- $.each(events[eventName], $.proxy(function(key, value)
- {
- if (value['namespace'] == eventNamespace)
- {
- var data = (typeof data == 'undefined') ? [e] : [e, data];
- returnValue = (typeof data == 'undefined') ? value.handler.call(this, e) : value.handler.call(this, e, data);
- }
- }, this));
- }
- if (returnValue) return returnValue;
- }
- if ($.isFunction(callback))
- {
- return (typeof data == 'undefined') ? callback.call(this, e) : callback.call(this, e, data);
- }
- else
- {
- return (typeof data == 'undefined') ? e : data;
- }
- },
- destroy: function()
- {
- this.opts.destroyed = true;
- this.core.setCallback('destroy');
- // off events and remove data
- this.$element.off('.redactor').removeData('redactor');
- this.$editor.off('.redactor');
- $(document).off('mousedown.redactor-blur.' + this.uuid);
- $(document).off('mousedown.redactor.' + this.uuid);
- $(document).off('click.redactor-image-delete.' + this.uuid);
- $(document).off('click.redactor-image-resize-hide.' + this.uuid);
- $(document).off('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid);
- $("body").off('scroll.redactor.' + this.uuid);
- $(this.opts.toolbarFixedTarget).off('scroll.redactor.' + this.uuid);
- // common
- this.$editor.removeClass('redactor-editor redactor-linebreaks redactor-placeholder');
- this.$editor.removeAttr('contenteditable');
- var html = this.code.get();
- if (this.opts.toolbar)
- {
- // dropdowns off
- this.$toolbar.find('a').each(function()
- {
- var $el = $(this);
- if ($el.data('dropdown'))
- {
- $el.data('dropdown').remove();
- $el.data('dropdown', {});
- }
- });
- }
- if (this.build.isTextarea())
- {
- this.$box.after(this.$element);
- this.$box.remove();
- this.$element.val(html).show();
- }
- else
- {
- this.$box.after(this.$editor);
- this.$box.remove();
- this.$element.html(html).show();
- }
- // paste box
- if (this.$pasteBox) this.$pasteBox.remove();
- // modal
- if (this.$modalBox) this.$modalBox.remove();
- if (this.$modalOverlay) this.$modalOverlay.remove();
- // buttons tooltip
- $('.redactor-toolbar-tooltip-' + this.uuid).remove();
- // autosave
- clearInterval(this.autosaveInterval);
- }
- };
- },
- dropdown: function()
- {
- return {
- build: function(name, $dropdown, dropdownObject)
- {
- if (name == 'formatting' && this.opts.formattingAdd)
- {
- $.each(this.opts.formattingAdd, $.proxy(function(i,s)
- {
- var name = s.tag,
- func;
- if (typeof s['class'] != 'undefined')
- {
- name = name + '-' + s['class'];
- }
- s.type = (this.utils.isBlockTag(s.tag)) ? 'block' : 'inline';
- if (typeof s.func !== "undefined")
- {
- func = s.func;
- }
- else
- {
- func = (s.type == 'inline') ? 'inline.formatting' : 'block.formatting';
- }
- if (this.opts.linebreaks && s.type == 'block' && s.tag == 'p') return;
- this.formatting[name] = {
- tag: s.tag,
- style: s.style,
- 'class': s['class'],
- attr: s.attr,
- data: s.data,
- clear: s.clear
- };
- dropdownObject[name] = {
- func: func,
- title: s.title
- };
- }, this));
- }
- $.each(dropdownObject, $.proxy(function(btnName, btnObject)
- {
- var $item = $('<a href="#" class="redactor-dropdown-' + btnName + '" role="button">' + btnObject.title + '</a>');
- if (name == 'formatting') $item.addClass('redactor-formatting-' + btnName);
- $item.on('click', $.proxy(function(e)
- {
- e.preventDefault();
- var type = 'func';
- var callback = btnObject.func;
- if (btnObject.command)
- {
- type = 'command';
- callback = btnObject.command;
- }
- else if (btnObject.dropdown)
- {
- type = 'dropdown';
- callback = btnObject.dropdown;
- }
- if ($(e.target).hasClass('redactor-dropdown-link-inactive')) return;
- this.button.onClick(e, btnName, type, callback);
- this.dropdown.hideAll();
- }, this));
- this.observe.addDropdown($item, btnName, btnObject);
- $dropdown.append($item);
- }, this));
- },
- show: function(e, key)
- {
- if (!this.opts.visual)
- {
- e.preventDefault();
- return false;
- }
- var $button = this.button.get(key);
- // Always re-append it to the end of <body> so it always has the highest sub-z-index.
- var $dropdown = $button.data('dropdown').appendTo(document.body);
- if (this.opts.highContrast)
- {
- $dropdown.addClass("redactor-dropdown-contrast");
- }
- if ($button.hasClass('dropact'))
- {
- this.dropdown.hideAll();
- }
- else
- {
- this.dropdown.hideAll();
- this.observe.dropdowns();
- this.core.setCallback('dropdownShow', { dropdown: $dropdown, key: key, button: $button });
- this.button.setActive(key);
- $button.addClass('dropact');
- var keyPosition = $button.offset();
- // fix right placement
- var dropdownWidth = $dropdown.width();
- if ((keyPosition.left + dropdownWidth) > $(document).width())
- {
- keyPosition.left = Math.max(0, keyPosition.left - dropdownWidth);
- }
- var left = keyPosition.left + 'px';
- if (this.$toolbar.hasClass('toolbar-fixed-box'))
- {
- var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
- var position = 'fixed';
- if (this.opts.toolbarFixedTarget !== document)
- {
- top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
- position = 'absolute';
- }
- $dropdown.css({ position: position, left: left, top: top + 'px' }).show();
- }
- else
- {
- var top = ($button.innerHeight() + keyPosition.top) + 'px';
- $dropdown.css({ position: 'absolute', left: left, top: top }).show();
- }
- this.core.setCallback('dropdownShown', { dropdown: $dropdown, key: key, button: $button });
- this.$dropdown = $dropdown;
- }
- $(document).one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
- this.$editor.one('click.redactor-dropdown', $.proxy(this.dropdown.hide, this));
- $(document).one('keyup.redactor-dropdown', $.proxy(this.dropdown.closeHandler, this));
- // disable scroll whan dropdown scroll
- $dropdown.on('mouseover.redactor-dropdown', $.proxy(this.utils.disableBodyScroll, this)).on('mouseout.redactor-dropdown', $.proxy(this.utils.enableBodyScroll, this));
- e.stopPropagation();
- },
- closeHandler: function(e)
- {
- if (e.which != this.keyCode.ESC) return;
- this.dropdown.hideAll();
- this.$editor.focus();
- },
- hideAll: function()
- {
- this.$toolbar.find('a.dropact').removeClass('redactor-act').removeClass('dropact');
- this.utils.enableBodyScroll();
- $('.redactor-dropdown-' + this.uuid).hide();
- $('.redactor-dropdown-link-selected').removeClass('redactor-dropdown-link-selected');
- if (this.$dropdown)
- {
- this.$dropdown.off('.redactor-dropdown');
- this.core.setCallback('dropdownHide', this.$dropdown);
- this.$dropdown = false;
- }
- },
- hide: function (e)
- {
- var $dropdown = $(e.target);
- if (!$dropdown.hasClass('dropact') && !$dropdown.hasClass('redactor-dropdown-link-inactive'))
- {
- if ($dropdown.hasClass('redactor-dropdown'))
- {
- $dropdown.removeClass('dropact');
- $dropdown.off('mouseover mouseout');
- }
- this.dropdown.hideAll();
- }
- }
- };
- },
- file: function()
- {
- return {
- show: function()
- {
- this.modal.load('file', this.lang.get('file'), 700);
- this.upload.init('#redactor-modal-file-upload', this.opts.fileUpload, this.file.insert);
- this.selection.save();
- this.selection.get();
- var text = this.sel.toString();
- $('#redactor-filename').val(text);
- this.modal.show();
- },
- insert: function(json, direct, e)
- {
- // error callback
- if (typeof json.error != 'undefined')
- {
- this.modal.close();
- this.selection.restore();
- this.core.setCallback('fileUploadError', json);
- return;
- }
- var link;
- if (typeof json == 'string')
- {
- link = json;
- }
- else
- {
- var text = $('#redactor-filename').val();
- if (typeof text == 'undefined' || text === '') text = json.filename;
- link = '<a href="' + json.filelink + '" id="filelink-marker">' + text + '</a>';
- }
- if (direct)
- {
- this.selection.removeMarkers();
- var marker = this.selection.getMarker();
- this.insert.nodeToCaretPositionFromPoint(e, marker);
- }
- else
- {
- this.modal.close();
- }
- this.selection.restore();
- this.buffer.set();
- this.insert.htmlWithoutClean(link);
- if (typeof json == 'string') return;
- var linkmarker = $(this.$editor.find('a#filelink-marker'));
- if (linkmarker.length !== 0)
- {
- linkmarker.removeAttr('id').removeAttr('style');
- }
- else linkmarker = false;
- this.core.setCallback('fileUpload', linkmarker, json);
- }
- };
- },
- focus: function()
- {
- return {
- setStart: function()
- {
- this.$editor.focus();
- var first = this.$editor.children().first();
- if (first.length === 0) return;
- if (first[0].length === 0 || first[0].tagName == 'BR' || first[0].nodeType == 3)
- {
- return;
- }
- if (first[0].tagName == 'UL' || first[0].tagName == 'OL')
- {
- var child = first.find('li').first();
- if (!this.utils.isBlock(child) && child.text() === '')
- {
- // empty inline tag in li
- this.caret.setStart(child);
- return;
- }
- }
- if (this.opts.linebreaks && !this.utils.isBlockTag(first[0].tagName))
- {
- this.selection.get();
- this.range.setStart(this.$editor[0], 0);
- this.range.setEnd(this.$editor[0], 0);
- this.selection.addRange();
- return;
- }
- // if node is tag
- this.caret.setStart(first);
- },
- setEnd: function()
- {
- var last = this.$editor.children().last();
- this.$editor.focus();
- if (last.size() === 0) return;
- if (this.utils.isEmpty(this.$editor.html()))
- {
- this.selection.get();
- this.range.collapse(true);
- this.range.setStartAfter(last[0]);
- this.range.setEnd(last[0], 0);
- this.selection.addRange();
- }
- else
- {
- this.selection.get();
- this.range.selectNodeContents(last[0]);
- this.range.collapse(false);
- this.selection.addRange();
- }
- },
- isFocused: function()
- {
- return this.$editor[0] === document.activeElement;
- }
- };
- },
- image: function()
- {
- return {
- show: function()
- {
- this.modal.load('image', this.lang.get('image'), 700);
- this.upload.init('#redactor-modal-image-droparea', this.opts.imageUpload, this.image.insert);
- this.selection.save();
- this.modal.show();
- },
- showEdit: function($image)
- {
- var $link = $image.closest('a', this.$editor[0]);
- this.modal.load('imageEdit', this.lang.get('edit'), 705);
- this.modal.createCancelButton();
- this.image.buttonDelete = this.modal.createDeleteButton(this.lang.get('_delete'));
- this.image.buttonSave = this.modal.createActionButton(this.lang.get('save'));
- this.image.buttonDelete.on('click', $.proxy(function()
- {
- this.image.remove($image);
- }, this));
- this.image.buttonSave.on('click', $.proxy(function()
- {
- this.image.update($image);
- }, this));
- // hide link's tooltip
- $('.redactor-link-tooltip').remove();
- $('#redactor-image-title').val($image.attr('alt'));
- if (!this.opts.imageLink) $('.redactor-image-link-option').hide();
- else
- {
- var $redactorImageLink = $('#redactor-image-link');
- $redactorImageLink.attr('href', $image.attr('src'));
- if ($link.length !== 0)
- {
- $redactorImageLink.val($link.attr('href'));
- if ($link.attr('target') == '_blank') $('#redactor-image-link-blank').prop('checked', true);
- }
- }
- if (!this.opts.imagePosition) $('.redactor-image-position-option').hide();
- else
- {
- var floatValue = ($image.css('display') == 'block' && $image.css('float') == 'none') ? 'center' : $image.css('float');
- $('#redactor-image-align').val(floatValue);
- }
- this.modal.show();
- $('#redactor-image-title').focus();
- },
- setFloating: function($image)
- {
- var floating = $('#redactor-image-align').val();
- var imageFloat = '';
- var imageDisplay = '';
- var imageMargin = '';
- switch (floating)
- {
- case 'left':
- imageFloat = 'left';
- imageMargin = '0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin + ' 0';
- break;
- case 'right':
- imageFloat = 'right';
- imageMargin = '0 0 ' + this.opts.imageFloatMargin + ' ' + this.opts.imageFloatMargin;
- break;
- case 'center':
- imageDisplay = 'block';
- imageMargin = 'auto';
- break;
- }
- $image.css({ 'float': imageFloat, display: imageDisplay, margin: imageMargin });
- $image.attr('rel', $image.attr('style'));
- },
- update: function($image)
- {
- this.image.hideResize();
- this.buffer.set();
- var $link = $image.closest('a', this.$editor[0]);
- var title = $('#redactor-image-title').val().replace(/(<([^>]+)>)/ig,"");
- $image.attr('alt', title);
- this.image.setFloating($image);
- // as link
- var link = $.trim($('#redactor-image-link').val());
- var link = link.replace(/(<([^>]+)>)/ig,"");
- if (link !== '')
- {
- // test url (add protocol)
- var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
- var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
- var re2 = new RegExp('^' + pattern, 'i');
- if (link.search(re) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
- {
- link = this.opts.linkProtocol + '://' + link;
- }
- var target = ($('#redactor-image-link-blank').prop('checked')) ? true : false;
- if ($link.length === 0)
- {
- var a = $('<a href="' + link + '">' + this.utils.getOuterHtml($image) + '</a>');
- if (target) a.attr('target', '_blank');
- $image.replaceWith(a);
- }
- else
- {
- $link.attr('href', link);
- if (target)
- {
- $link.attr('target', '_blank');
- }
- else
- {
- $link.removeAttr('target');
- }
- }
- }
- else if ($link.length !== 0)
- {
- $link.replaceWith(this.utils.getOuterHtml($image));
- }
- this.modal.close();
- this.observe.images();
- this.code.sync();
- },
- setEditable: function($image)
- {
- if (this.opts.imageEditable)
- {
- $image.on('dragstart', $.proxy(this.image.onDrag, this));
- }
- var handler = $.proxy(function(e)
- {
- this.observe.image = $image;
- this.image.resizer = this.image.loadEditableControls($image);
- $(document).on('mousedown.redactor-image-resize-hide.' + this.uuid, $.proxy(this.image.hideResize, this));
- // resize
- if (!this.opts.imageResizable) return;
- this.image.resizer.on('mousedown.redactor touchstart.redactor', $.proxy(function(e)
- {
- this.image.setResizable(e, $image);
- }, this));
- }, this);
- $image.off('mousedown.redactor').on('mousedown.redactor', $.proxy(this.image.hideResize, this));
- $image.off('click.redactor touchstart.redactor').on('click.redactor touchstart.redactor', handler);
- },
- setResizable: function(e, $image)
- {
- e.preventDefault();
- this.image.resizeHandle = {
- x : e.pageX,
- y : e.pageY,
- el : $image,
- ratio: $image.width() / $image.height(),
- h: $image.height()
- };
- e = e.originalEvent || e;
- if (e.targetTouches)
- {
- this.image.resizeHandle.x = e.targetTouches[0].pageX;
- this.image.resizeHandle.y = e.targetTouches[0].pageY;
- }
- this.image.startResize();
- },
- startResize: function()
- {
- $(document).on('mousemove.redactor-image-resize touchmove.redactor-image-resize', $.proxy(this.image.moveResize, this));
- $(document).on('mouseup.redactor-image-resize touchend.redactor-image-resize', $.proxy(this.image.stopResize, this));
- },
- moveResize: function(e)
- {
- e.preventDefault();
- e = e.originalEvent || e;
- var height = this.image.resizeHandle.h;
- if (e.targetTouches) height += (e.targetTouches[0].pageY - this.image.resizeHandle.y);
- else height += (e.pageY - this.image.resizeHandle.y);
- var width = Math.round(height * this.image.resizeHandle.ratio);
- if (height < 50 || width < 100) return;
- var height = Math.round(this.image.resizeHandle.el.width() / this.image.resizeHandle.ratio);
- this.image.resizeHandle.el.attr({width: width, height: height});
- this.image.resizeHandle.el.width(width);
- this.image.resizeHandle.el.height(height);
- this.code.sync();
- },
- stopResize: function()
- {
- this.handle = false;
- $(document).off('.redactor-image-resize');
- this.image.hideResize();
- },
- onDrag: function(e)
- {
- if (this.$editor.find('#redactor-image-box').length !== 0)
- {
- e.preventDefault();
- return false;
- }
- this.$editor.on('drop.redactor-image-inside-drop', $.proxy(function()
- {
- setTimeout($.proxy(this.image.onDrop, this), 1);
- }, this));
- },
- onDrop: function()
- {
- this.image.fixImageSourceAfterDrop();
- this.observe.images();
- this.$editor.off('drop.redactor-image-inside-drop');
- this.clean.clearUnverified();
- this.code.sync();
- },
- fixImageSourceAfterDrop: function()
- {
- this.$editor.find('img[data-save-url]').each(function()
- {
- var $el = $(this);
- $el.attr('src', $el.attr('data-save-url'));
- $el.removeAttr('data-save-url');
- });
- },
- hideResize: function(e)
- {
- if (e && $(e.target).closest('#redactor-image-box', this.$editor[0]).length !== 0) return;
- if (e && e.target.tagName == 'IMG')
- {
- var $image = $(e.target);
- $image.attr('data-save-url', $image.attr('src'));
- }
- var imageBox = this.$editor.find('#redactor-image-box');
- if (imageBox.length === 0) return;
- $('#redactor-image-editter').remove();
- $('#redactor-image-resizer').remove();
- imageBox.find('img').css({
- marginTop: imageBox[0].style.marginTop,
- marginBottom: imageBox[0].style.marginBottom,
- marginLeft: imageBox[0].style.marginLeft,
- marginRight: imageBox[0].style.marginRight
- });
- imageBox.css('margin', '');
- imageBox.find('img').css('opacity', '');
- imageBox.replaceWith(function()
- {
- return $(this).contents();
- });
- $(document).off('mousedown.redactor-image-resize-hide.' + this.uuid);
- if (typeof this.image.resizeHandle !== 'undefined')
- {
- this.image.resizeHandle.el.attr('rel', this.image.resizeHandle.el.attr('style'));
- }
- this.code.sync();
- },
- loadResizableControls: function($image, imageBox)
- {
- if (this.opts.imageResizable && !this.utils.isMobile())
- {
- var imageResizer = $('<span id="redactor-image-resizer" data-redactor="verified"></span>');
- if (!this.utils.isDesktop())
- {
- imageResizer.css({ width: '15px', height: '15px' });
- }
- imageResizer.attr('contenteditable', false);
- imageBox.append(imageResizer);
- imageBox.append($image);
- return imageResizer;
- }
- else
- {
- imageBox.append($image);
- return false;
- }
- },
- loadEditableControls: function($image)
- {
- var imageBox = $('<span id="redactor-image-box" data-redactor="verified">');
- imageBox.css('float', $image.css('float')).attr('contenteditable', false);
- if ($image[0].style.margin != 'auto')
- {
- imageBox.css({
- marginTop: $image[0].style.marginTop,
- marginBottom: $image[0].style.marginBottom,
- marginLeft: $image[0].style.marginLeft,
- marginRight: $image[0].style.marginRight
- });
- $image.css('margin', '');
- }
- else
- {
- imageBox.css({ 'display': 'block', 'margin': 'auto' });
- }
- $image.css('opacity', '.5').after(imageBox);
- if (this.opts.imageEditable)
- {
- // editter
- this.image.editter = $('<span id="redactor-image-editter" data-redactor="verified">' + this.lang.get('edit') + '</span>');
- this.image.editter.attr('contenteditable', false);
- this.image.editter.on('click', $.proxy(function()
- {
- this.image.showEdit($image);
- }, this));
- imageBox.append(this.image.editter);
- // position correction
- var editerWidth = this.image.editter.innerWidth();
- this.image.editter.css('margin-left', '-' + editerWidth/2 + 'px');
- }
- return this.image.loadResizableControls($image, imageBox);
- },
- remove: function(image)
- {
- var $image = $(image);
- var $link = $image.closest('a', this.$editor[0]);
- var $figure = $image.closest('figure', this.$editor[0]);
- var $parent = $image.parent();
- if ($('#redactor-image-box').length !== 0)
- {
- $parent = $('#redactor-image-box').parent();
- }
- var $next;
- if ($figure.length !== 0)
- {
- $next = $figure.next();
- $figure.remove();
- }
- else if ($link.length !== 0)
- {
- $parent = $link.parent();
- $link.remove();
- }
- else
- {
- $image.remove();
- }
- $('#redactor-image-box').remove();
- if ($figure.length !== 0)
- {
- this.caret.setStart($next);
- }
- else
- {
- this.caret.setStart($parent);
- }
- // delete callback
- this.core.setCallback('imageDelete', $image[0].src, $image);
- this.modal.close();
- this.code.sync();
- },
- insert: function(json, direct, e)
- {
- // error callback
- if (typeof json.error != 'undefined')
- {
- this.modal.close();
- this.selection.restore();
- this.core.setCallback('imageUploadError', json);
- return;
- }
- var $img;
- if (typeof json == 'string')
- {
- $img = $(json).attr('data-redactor-inserted-image', 'true');
- }
- else
- {
- $img = $('<img>');
- $img.attr('src', json.filelink).attr('data-redactor-inserted-image', 'true');
- }
- var node = $img;
- var isP = this.utils.isCurrentOrParent('P');
- if (isP)
- {
- // will replace
- node = $('<blockquote />').append($img);
- }
- if (direct)
- {
- this.selection.removeMarkers();
- var marker = this.selection.getMarker();
- this.insert.nodeToCaretPositionFromPoint(e, marker);
- }
- else
- {
- this.modal.close();
- }
- this.selection.restore();
- this.buffer.set();
- this.insert.html(this.utils.getOuterHtml(node), false);
- var $image = this.$editor.find('img[data-redactor-inserted-image=true]').removeAttr('data-redactor-inserted-image');
- if (isP)
- {
- $image.parent().contents().unwrap().wrap('<p />');
- }
- else if (this.opts.linebreaks)
- {
- if (!this.utils.isEmpty(this.code.get()))
- {
- $image.before('<br>');
- }
- $image.after('<br>');
- }
- if (typeof json == 'string') return;
- this.core.setCallback('imageUpload', $image, json);
- }
- };
- },
- indent: function()
- {
- return {
- increase: function()
- {
- // focus
- if (!this.utils.browser('msie')) this.$editor.focus();
- this.buffer.set();
- this.selection.save();
- var block = this.selection.getBlock();
- if (block && block.tagName == 'LI')
- {
- this.indent.increaseLists();
- }
- else if (block === false && this.opts.linebreaks)
- {
- this.indent.increaseText();
- }
- else
- {
- this.indent.increaseBlocks();
- }
- this.selection.restore();
- this.code.sync();
- },
- increaseLists: function()
- {
- document.execCommand('indent');
- this.indent.fixEmptyIndent();
- this.clean.normalizeLists();
- this.clean.clearUnverified();
- },
- increaseBlocks: function()
- {
- $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
- {
- if (elem.tagName === 'TD' || elem.tagName === 'TH') return;
- var $el = this.utils.getAlignmentElement(elem);
- var left = this.utils.normalize($el.css('margin-left')) + this.opts.indentValue;
- $el.css('margin-left', left + 'px');
- }, this));
- },
- increaseText: function()
- {
- var wrapper = this.selection.wrap('div');
- $(wrapper).attr('data-tagblock', 'redactor');
- $(wrapper).css('margin-left', this.opts.indentValue + 'px');
- },
- decrease: function()
- {
- this.buffer.set();
- this.selection.save();
- var block = this.selection.getBlock();
- if (block && block.tagName == 'LI')
- {
- this.indent.decreaseLists();
- }
- else
- {
- this.indent.decreaseBlocks();
- }
- this.selection.restore();
- this.code.sync();
- },
- decreaseLists: function()
- {
- document.execCommand('outdent');
- var current = this.selection.getCurrent();
- var $item = $(current).closest('li', this.$editor[0]);
- this.indent.fixEmptyIndent();
- if (!this.opts.linebreaks && $item.length === 0)
- {
- document.execCommand('formatblock', false, 'p');
- this.$editor.find('ul, ol, blockquote, p').each($.proxy(this.utils.removeEmpty, this));
- }
- this.clean.clearUnverified();
- },
- decreaseBlocks: function()
- {
- $.each(this.selection.getBlocks(), $.proxy(function(i, elem)
- {
- var $el = this.utils.getAlignmentElement(elem);
- var left = this.utils.normalize($el.css('margin-left')) - this.opts.indentValue;
- if (left <= 0)
- {
- if (this.opts.linebreaks && typeof($el.data('tagblock')) !== 'undefined')
- {
- $el.replaceWith($el.html() + '<br />');
- }
- else
- {
- $el.css('margin-left', '');
- this.utils.removeEmptyAttr($el, 'style');
- }
- }
- else
- {
- $el.css('margin-left', left + 'px');
- }
- }, this));
- },
- fixEmptyIndent: function()
- {
- var block = this.selection.getBlock();
- if (this.range.collapsed && block && block.tagName == 'LI' && this.utils.isEmpty($(block).text()))
- {
- var $block = $(block);
- $block.find('span').not('.redactor-selection-marker').contents().unwrap();
- $block.append('<br>');
- }
- }
- };
- },
- inline: function()
- {
- return {
- formatting: function(name)
- {
- var type, value;
- if (typeof this.formatting[name].style != 'undefined') type = 'style';
- else if (typeof this.formatting[name]['class'] != 'undefined') type = 'class';
- if (type) value = this.formatting[name][type];
- this.inline.format(this.formatting[name].tag, type, value);
- },
- format: function(tag, type, value)
- {
- var current = this.selection.getCurrent();
- if (current && current.tagName === 'TR') return;
- // Stop formatting pre and headers
- if (this.utils.isCurrentOrParent('PRE') || this.utils.isCurrentOrParentHeader()) return;
- var tags = ['b', 'bold', 'i', 'italic', 'underline', 'strikethrough', 'deleted', 'superscript', 'subscript'];
- var replaced = ['strong', 'strong', 'em', 'em', 'u', 'del', 'del', 'sup', 'sub'];
- for (var i = 0; i < tags.length; i++)
- {
- if (tag == tags[i]) tag = replaced[i];
- }
- if (this.opts.allowedTags)
- {
- if ($.inArray(tag, this.opts.allowedTags) == -1) return;
- }
- else
- {
- if ($.inArray(tag, this.opts.deniedTags) !== -1) return;
- }
- this.inline.type = type || false;
- this.inline.value = value || false;
- this.buffer.set();
- if (!this.utils.browser('msie') && !this.opts.linebreaks)
- {
- this.$editor.focus();
- }
- this.selection.get();
- if (this.range.collapsed)
- {
- this.inline.formatCollapsed(tag);
- }
- else
- {
- this.inline.formatMultiple(tag);
- }
- },
- formatCollapsed: function(tag)
- {
- var current = this.selection.getCurrent();
- var $parent = $(current).closest(tag + '[data-redactor-tag=' + tag + ']', this.$editor[0]);
- // inline there is
- if ($parent.length !== 0 && (this.inline.type != 'style' && $parent[0].tagName != 'SPAN'))
- {
- // remove empty
- if (this.utils.isEmpty($parent.text()))
- {
- this.caret.setAfter($parent[0]);
- $parent.remove();
- this.code.sync();
- }
- else if (this.utils.isEndOfElement($parent))
- {
- this.caret.setAfter($parent[0]);
- }
- return;
- }
- // create empty inline
- var node = $('<' + tag + '>').attr('data-verified', 'redactor').attr('data-redactor-tag', tag);
- node.html(this.opts.invisibleSpace);
- node = this.inline.setFormat(node);
- var node = this.insert.node(node);
- this.caret.setEnd(node);
- this.code.sync();
- },
- formatMultiple: function(tag)
- {
- this.inline.formatConvert(tag);
- this.selection.save();
- document.execCommand('strikethrough');
- this.$editor.find('strike').each($.proxy(function(i,s)
- {
- var $el = $(s);
- this.inline.formatRemoveSameChildren($el, tag);
- var $span;
- if (this.inline.type)
- {
- $span = $('<span>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
- $span = this.inline.setFormat($span);
- }
- else
- {
- $span = $('<' + tag + '>').attr('data-redactor-tag', tag).attr('data-verified', 'redactor');
- }
- $el.replaceWith($span.html($el.contents()));
- var $parent = $span.parent();
- // remove U tag if selected link + node
- if ($span[0].tagName === 'A' && $parent && $parent[0].tagName === 'U')
- {
- $span.parent().replaceWith($span);
- }
- if (tag == 'span')
- {
- if ($parent && $parent[0].tagName === 'SPAN' && this.inline.type === 'style')
- {
- var arr = this.inline.value.split(';');
- for (var z = 0; z < arr.length; z++)
- {
- if (arr[z] === '') return;
- var style = arr[z].split(':');
- $parent.css(style[0], '');
- if (this.utils.removeEmptyAttr($parent, 'style'))
- {
- $parent.replaceWith($parent.contents());
- }
- }
- }
- }
- }, this));
- // clear text decoration
- if (tag != 'span')
- {
- this.$editor.find(this.opts.inlineTags.join(', ')).each($.proxy(function(i,s)
- {
- var $el = $(s);
- if (s.tagName === 'U' && s.attributes.length === 0)
- {
- $el.replaceWith($el.contents());
- return;
- }
- var property = $el.css('text-decoration');
- if (property === 'line-through')
- {
- $el.css('text-decoration', '');
- this.utils.removeEmptyAttr($el, 'style');
- }
- }, this));
- }
- if (tag != 'del')
- {
- var _this = this;
- this.$editor.find('inline').each(function(i,s)
- {
- _this.utils.replaceToTag(s, 'del');
- });
- }
- if (tag != 'u')
- {
- var _this = this;
- this.$editor.find('unline').each(function(i,s)
- {
- _this.utils.replaceToTag(s, 'u');
- });
- }
- this.selection.restore();
- this.code.sync();
- },
- formatRemoveSameChildren: function($el, tag)
- {
- var self = this;
- $el.children(tag).each(function()
- {
- var $child = $(this);
- if (!$child.hasClass('redactor-selection-marker'))
- {
- if (self.inline.type == 'style')
- {
- var arr = self.inline.value.split(';');
- for (var z = 0; z < arr.length; z++)
- {
- if (arr[z] === '') return;
- var style = arr[z].split(':');
- $child.css(style[0], '');
- if (self.utils.removeEmptyAttr($child , 'style'))
- {
- $child.replaceWith($child.contents());
- }
- }
- }
- else
- {
- $child.contents().unwrap();
- }
- }
- });
- },
- formatConvert: function(tag)
- {
- this.selection.save();
- var find = '';
- if (this.inline.type == 'class') find = '[data-redactor-class=' + this.inline.value + ']';
- else if (this.inline.type == 'style')
- {
- find = '[data-redactor-style="' + this.inline.value + '"]';
- }
- var self = this;
- if (tag != 'del')
- {
- this.$editor.find('del').each(function(i,s)
- {
- self.utils.replaceToTag(s, 'inline');
- });
- }
- if (tag != 'u')
- {
- this.$editor.find('u').each(function(i,s)
- {
- self.utils.replaceToTag(s, 'unline');
- });
- }
- if (tag != 'span')
- {
- this.$editor.find(tag).each(function()
- {
- var $el = $(this);
- $el.replaceWith($('<strike />').html($el.contents()));
- });
- }
- this.$editor.find('[data-redactor-tag="' + tag + '"]' + find).each(function()
- {
- if (find === '' && tag == 'span' && this.tagName.toLowerCase() == tag) return;
- var $el = $(this);
- $el.replaceWith($('<strike />').html($el.contents()));
- });
- this.selection.restore();
- },
- setFormat: function(node)
- {
- switch (this.inline.type)
- {
- case 'class':
- if (node.hasClass(this.inline.value))
- {
- node.removeClass(this.inline.value);
- node.removeAttr('data-redactor-class');
- }
- else
- {
- node.addClass(this.inline.value);
- node.attr('data-redactor-class', this.inline.value);
- }
- break;
- case 'style':
- node[0].style.cssText = this.inline.value;
- node.attr('data-redactor-style', this.inline.value);
- break;
- }
- return node;
- },
- removeStyle: function()
- {
- this.buffer.set();
- var current = this.selection.getCurrent();
- var nodes = this.selection.getInlines();
- this.selection.save();
- if (current && current.tagName === 'SPAN')
- {
- var $s = $(current);
- $s.removeAttr('style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
- }
- $.each(nodes, $.proxy(function(i,s)
- {
- var $s = $(s);
- if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
- {
- $s.removeAttr('style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
- }
- }, this));
- this.selection.restore();
- this.code.sync();
- },
- removeStyleRule: function(name)
- {
- this.buffer.set();
- var parent = this.selection.getParent();
- var nodes = this.selection.getInlines();
- this.selection.save();
- if (parent && parent.tagName === 'SPAN')
- {
- var $s = $(parent);
- $s.css(name, '');
- this.utils.removeEmptyAttr($s, 'style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
- }
- $.each(nodes, $.proxy(function(i,s)
- {
- var $s = $(s);
- if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
- {
- $s.css(name, '');
- this.utils.removeEmptyAttr($s, 'style');
- if ($s[0].attributes.length === 0)
- {
- $s.replaceWith($s.contents());
- }
- }
- }, this));
- this.selection.restore();
- this.code.sync();
- },
- removeFormat: function()
- {
- this.buffer.set();
- var current = this.selection.getCurrent();
- this.selection.save();
- document.execCommand('removeFormat');
- if (current && current.tagName === 'SPAN')
- {
- $(current).replaceWith($(current).contents());
- }
- $.each(this.selection.getNodes(), $.proxy(function(i,s)
- {
- var $s = $(s);
- if ($.inArray(s.tagName.toLowerCase(), this.opts.inlineTags) != -1 && !$s.hasClass('redactor-selection-marker'))
- {
- $s.replaceWith($s.contents());
- }
- }, this));
- this.selection.restore();
- this.code.sync();
- },
- toggleClass: function(className)
- {
- this.inline.format('span', 'class', className);
- },
- toggleStyle: function(value)
- {
- this.inline.format('span', 'style', value);
- }
- };
- },
- insert: function()
- {
- return {
- set: function(html, clean)
- {
- this.placeholder.remove();
- html = this.clean.setVerified(html);
- if (typeof clean == 'undefined')
- {
- html = this.clean.onPaste(html, false);
- }
- this.$editor.html(html);
- this.selection.remove();
- this.focus.setEnd();
- this.clean.normalizeLists();
- this.code.sync();
- this.observe.load();
- if (typeof clean == 'undefined')
- {
- setTimeout($.proxy(this.clean.clearUnverified, this), 10);
- }
- },
- text: function(text)
- {
- this.placeholder.remove();
- text = text.toString();
- text = $.trim(text);
- text = this.clean.getPlainText(text, false);
- this.$editor.focus();
- if (this.utils.browser('msie'))
- {
- this.insert.htmlIe(text);
- }
- else
- {
- this.selection.get();
- this.range.deleteContents();
- var el = document.createElement("div");
- el.innerHTML = text;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
- this.range.insertNode(frag);
- if (lastNode)
- {
- var range = this.range.cloneRange();
- range.setStartAfter(lastNode);
- range.collapse(true);
- this.sel.removeAllRanges();
- this.sel.addRange(range);
- }
- }
- this.code.sync();
- this.clean.clearUnverified();
- },
- htmlWithoutClean: function(html)
- {
- this.insert.html(html, false);
- },
- html: function(html, clean)
- {
- this.placeholder.remove();
- if (typeof clean == 'undefined') clean = true;
- if (!this.opts.linebreaks)
- {
- this.$editor.focus();
- }
- html = this.clean.setVerified(html);
- if (clean)
- {
- html = this.clean.onPaste(html);
- }
- if (this.utils.browser('msie'))
- {
- this.insert.htmlIe(html);
- }
- else
- {
- if (this.clean.singleLine) this.insert.execHtml(html);
- else document.execCommand('insertHTML', false, html);
- this.insert.htmlFixMozilla();
- }
- this.clean.normalizeLists();
- // remove empty paragraphs finaly
- if (!this.opts.linebreaks)
- {
- this.$editor.find('p').each($.proxy(this.utils.removeEmpty, this));
- }
- this.code.sync();
- this.observe.load();
- if (clean)
- {
- this.clean.clearUnverified();
- }
- },
- htmlFixMozilla: function()
- {
- // FF inserts empty p when content was selected dblclick
- if (!this.utils.browser('mozilla')) return;
- var $next = $(this.selection.getBlock()).next();
- if ($next.length > 0 && $next[0].tagName == 'P' && $next.html() === '')
- {
- $next.remove();
- }
- },
- htmlIe: function(html)
- {
- if (this.utils.isIe11())
- {
- var parent = this.utils.isCurrentOrParent('P');
- var $html = $('<div>').append(html);
- var blocksMatch = $html.contents().is('p, :header, dl, ul, ol, div, table, td, blockquote, pre, address, section, header, footer, aside, article');
- if (parent && blocksMatch) this.insert.ie11FixInserting(parent, html);
- else this.insert.ie11PasteFrag(html);
- return;
- }
- document.selection.createRange().pasteHTML(html);
- },
- execHtml: function(html)
- {
- html = this.clean.setVerified(html);
- this.selection.get();
- this.range.deleteContents();
- var el = document.createElement('div');
- el.innerHTML = html;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
- this.range.insertNode(frag);
- this.range.collapse(true);
- this.caret.setAfter(lastNode);
- },
- node: function(node, deleteContents)
- {
- node = node[0] || node;
- var offset = this.caret.getOffset();
- var html = this.utils.getOuterHtml(node);
- html = this.clean.setVerified(html);
- if (html.match(/</g) !== null)
- {
- node = $(html)[0];
- }
- this.selection.get();
- if (deleteContents !== false)
- {
- this.range.deleteContents();
- }
- this.range.insertNode(node);
- this.range.collapse(false);
- this.selection.addRange();
- this.caret.setOffset(offset);
- return node;
- },
- nodeToPoint: function(node, x, y)
- {
- node = node[0] || node;
- this.selection.get();
- var range;
- if (document.caretPositionFromPoint)
- {
- var pos = document.caretPositionFromPoint(x, y);
- this.range.setStart(pos.offsetNode, pos.offset);
- this.range.collapse(true);
- this.range.insertNode(node);
- }
- else if (document.caretRangeFromPoint)
- {
- range = document.caretRangeFromPoint(x, y);
- range.insertNode(node);
- }
- else if (typeof document.body.createTextRange != "undefined")
- {
- range = document.body.createTextRange();
- range.moveToPoint(x, y);
- var endRange = range.duplicate();
- endRange.moveToPoint(x, y);
- range.setEndPoint("EndToEnd", endRange);
- range.select();
- }
- },
- nodeToCaretPositionFromPoint: function(e, node)
- {
- node = node[0] || node;
- var range;
- var x = e.clientX, y = e.clientY;
- if (document.caretPositionFromPoint)
- {
- var pos = document.caretPositionFromPoint(x, y);
- var sel = document.getSelection();
- range = sel.getRangeAt(0);
- range.setStart(pos.offsetNode, pos.offset);
- range.collapse(true);
- range.insertNode(node);
- }
- else if (document.caretRangeFromPoint)
- {
- range = document.caretRangeFromPoint(x, y);
- range.insertNode(node);
- }
- else if (typeof document.body.createTextRange != "undefined")
- {
- range = document.body.createTextRange();
- range.moveToPoint(x, y);
- var endRange = range.duplicate();
- endRange.moveToPoint(x, y);
- range.setEndPoint("EndToEnd", endRange);
- range.select();
- }
- },
- ie11FixInserting: function(parent, html)
- {
- var node = document.createElement('span');
- node.className = 'redactor-ie-paste';
- this.insert.node(node);
- var parHtml = $(parent).html();
- parHtml = '<p>' + parHtml.replace(/<span class="redactor-ie-paste"><\/span>/gi, '</p>' + html + '<p>') + '</p>';
- parHtml = parHtml.replace(/<p><\/p>/gi, '');
- $(parent).replaceWith(parHtml);
- },
- ie11PasteFrag: function(html)
- {
- this.selection.get();
- this.range.deleteContents();
- var el = document.createElement("div");
- el.innerHTML = html;
- var frag = document.createDocumentFragment(), node, lastNode;
- while ((node = el.firstChild))
- {
- lastNode = frag.appendChild(node);
- }
- this.range.insertNode(frag);
- this.range.collapse(false);
- this.selection.addRange();
- }
- };
- },
- keydown: function()
- {
- return {
- init: function(e)
- {
- if (this.rtePaste) return;
- var key = e.which;
- var arrow = (key >= 37 && key <= 40);
- this.keydown.ctrl = e.ctrlKey || e.metaKey;
- this.keydown.current = this.selection.getCurrent();
- this.keydown.parent = this.selection.getParent();
- this.keydown.block = this.selection.getBlock();
- // detect tags
- this.keydown.pre = this.utils.isTag(this.keydown.current, 'pre');
- this.keydown.blockquote = this.utils.isTag(this.keydown.current, 'blockquote');
- this.keydown.figcaption = this.utils.isTag(this.keydown.current, 'figcaption');
- // shortcuts setup
- this.shortcuts.init(e, key);
- if (this.utils.isDesktop())
- {
- this.keydown.checkEvents(arrow, key);
- this.keydown.setupBuffer(e, key);
- }
- this.keydown.addArrowsEvent(arrow);
- this.keydown.setupSelectAll(e, key);
- // callback
- var keydownStop = this.core.setCallback('keydown', e);
- if (keydownStop === false)
- {
- e.preventDefault();
- return false;
- }
- // ie and ff exit from table
- if (this.opts.enterKey && (this.utils.browser('msie') || this.utils.browser('mozilla')) && (key === this.keyCode.DOWN || key === this.keyCode.RIGHT))
- {
- var isEndOfTable = false;
- var $table = false;
- if (this.keydown.block && this.keydown.block.tagName === 'TD')
- {
- $table = $(this.keydown.block).closest('table', this.$editor[0]);
- }
- if ($table && $table.find('td').last()[0] === this.keydown.block)
- {
- isEndOfTable = true;
- }
- if (this.utils.isEndOfElement() && isEndOfTable)
- {
- var node = $(this.opts.emptyHtml);
- $table.after(node);
- this.caret.setStart(node);
- }
- }
- // down
- if (this.opts.enterKey && key === this.keyCode.DOWN)
- {
- this.keydown.onArrowDown();
- }
- // turn off enter key
- if (!this.opts.enterKey && key === this.keyCode.ENTER)
- {
- e.preventDefault();
- // remove selected
- if (!this.range.collapsed) this.range.deleteContents();
- return;
- }
- // on enter
- if (key == this.keyCode.ENTER && !e.shiftKey && !e.ctrlKey && !e.metaKey)
- {
- var stop = this.core.setCallback('enter', e);
- if (stop === false)
- {
- e.preventDefault();
- return false;
- }
- if (this.keydown.blockquote && this.keydown.exitFromBlockquote(e) === true)
- {
- return false;
- }
- var current, $next;
- if (this.keydown.pre)
- {
- return this.keydown.insertNewLine(e);
- }
- else if (this.keydown.blockquote || this.keydown.figcaption)
- {
- current = this.selection.getCurrent();
- $next = $(current).next();
- if ($next.length !== 0 && $next[0].tagName == 'BR')
- {
- return this.keydown.insertBreakLine(e);
- }
- else if (this.utils.isEndOfElement() && (current && current != 'SPAN'))
- {
- return this.keydown.insertDblBreakLine(e);
- }
- else
- {
- return this.keydown.insertBreakLine(e);
- }
- }
- else if (this.opts.linebreaks && !this.keydown.block)
- {
- current = this.selection.getCurrent();
- $next = $(this.keydown.current).next();
- if ($next.length !== 0 && $next[0].tagName == 'BR')
- {
- return this.keydown.insertBreakLine(e);
- }
- else if (current !== false && $(current).hasClass('redactor-invisible-space'))
- {
- this.caret.setAfter(current);
- $(current).contents().unwrap();
- return this.keydown.insertDblBreakLine(e);
- }
- else
- {
- if (this.utils.isEndOfEditor())
- {
- return this.keydown.insertDblBreakLine(e);
- }
- else if ($next.length === 0 && current === false && typeof $next.context != 'undefined')
- {
- return this.keydown.insertBreakLine(e);
- }
- return this.keydown.insertBreakLine(e);
- }
- }
- else if (this.opts.linebreaks && this.keydown.block)
- {
- setTimeout($.proxy(this.keydown.replaceDivToBreakLine, this), 1);
- }
- // paragraphs
- else if (!this.opts.linebreaks && this.keydown.block)
- {
- setTimeout($.proxy(this.keydown.replaceDivToParagraph, this), 1);
- if (this.keydown.block.tagName === 'LI')
- {
- current = this.selection.getCurrent();
- var $parent = $(current).closest('li', this.$editor[0]);
- var $list = $parent.closest('ul,ol', this.$editor[0]);
- if ($parent.length !== 0 && this.utils.isEmpty($parent.html()) && $list.next().length === 0 && this.utils.isEmpty($list.find("li").last().html()))
- {
- $list.find("li").last().remove();
- var node = $(this.opts.emptyHtml);
- $list.after(node);
- this.caret.setStart(node);
- return false;
- }
- }
- }
- else if (!this.opts.linebreaks && !this.keydown.block)
- {
- return this.keydown.insertParagraph(e);
- }
- }
- // Shift+Enter or Ctrl+Enter
- if (key === this.keyCode.ENTER && (e.ctrlKey || e.shiftKey))
- {
- return this.keydown.onShiftEnter(e);
- }
- // tab or cmd + [
- if (key === this.keyCode.TAB || e.metaKey && key === 221 || e.metaKey && key === 219)
- {
- return this.keydown.onTab(e, key);
- }
- // image delete and backspace
- if (key === this.keyCode.BACKSPACE || key === this.keyCode.DELETE)
- {
- var nodes = this.selection.getNodes();
- if (nodes)
- {
- var len = nodes.length;
- var last;
- for (var i = 0; i < len; i++)
- {
- var children = $(nodes[i]).children('img');
- if (children.length !== 0)
- {
- var self = this;
- $.each(children, function(z,s)
- {
- var $s = $(s);
- if ($s.css('float') != 'none') return;
- // image delete callback
- self.core.setCallback('imageDelete', s.src, $s);
- last = s;
- });
- }
- else if (nodes[i].tagName == 'IMG')
- {
- if (last != nodes[i])
- {
- // image delete callback
- this.core.setCallback('imageDelete', nodes[i].src, $(nodes[i]));
- last = nodes[i];
- }
- }
- }
- }
- }
- // backspace
- if (key === this.keyCode.BACKSPACE)
- {
- // backspace as outdent
- var block = this.selection.getBlock();
- var indented = ($(block).css('margin-left') !== '0px');
- if (block && indented && this.range.collapsed && this.utils.isStartOfElement())
- {
- this.indent.decrease();
- e.preventDefault();
- return;
- }
- // remove hr in FF
- if (this.utils.browser('mozilla'))
- {
- var prev = this.selection.getPrev();
- var prev2 = $(prev).prev()[0];
- if (prev && prev.tagName === 'HR') $(prev).remove();
- if (prev2 && prev2.tagName === 'HR') $(prev2).remove();
- }
- this.keydown.removeInvisibleSpace();
- this.keydown.removeEmptyListInTable(e);
- }
- this.code.sync();
- },
- checkEvents: function(arrow, key)
- {
- if (!arrow && (this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
- {
- this.core.addEvent(false);
- if (this.keydown.checkKeyEvents(key))
- {
- this.buffer.set();
- }
- }
- },
- checkKeyEvents: function(key)
- {
- var k = this.keyCode;
- var keys = [k.BACKSPACE, k.DELETE, k.ENTER, k.ESC, k.TAB, k.CTRL, k.META, k.ALT, k.SHIFT];
- return ($.inArray(key, keys) == -1) ? true : false;
- },
- addArrowsEvent: function(arrow)
- {
- if (!arrow) return;
- if ((this.core.getEvent() == 'click' || this.core.getEvent() == 'arrow'))
- {
- this.core.addEvent(false);
- return;
- }
- this.core.addEvent('arrow');
- },
- setupBuffer: function(e, key)
- {
- if (this.keydown.ctrl && key === 90 && !e.shiftKey && !e.altKey && this.opts.buffer.length) // z key
- {
- e.preventDefault();
- this.buffer.undo();
- return;
- }
- // undo
- else if (this.keydown.ctrl && key === 90 && e.shiftKey && !e.altKey && this.opts.rebuffer.length !== 0)
- {
- e.preventDefault();
- this.buffer.redo();
- return;
- }
- else if (!this.keydown.ctrl)
- {
- if (key == this.keyCode.BACKSPACE || key == this.keyCode.DELETE || (key == this.keyCode.ENTER && !e.ctrlKey && !e.shiftKey))
- {
- this.buffer.set();
- }
- }
- },
- setupSelectAll: function(e, key)
- {
- if (this.keydown.ctrl && key === 65)
- {
- this.utils.enableSelectAll();
- }
- else if (key != this.keyCode.LEFT_WIN && !this.keydown.ctrl)
- {
- this.utils.disableSelectAll();
- }
- },
- onArrowDown: function()
- {
- var tags = [this.keydown.blockquote, this.keydown.pre, this.keydown.figcaption];
- for (var i = 0; i < tags.length; i++)
- {
- if (tags[i])
- {
- this.keydown.insertAfterLastElement(tags[i]);
- return false;
- }
- }
- },
- onShiftEnter: function(e)
- {
- this.buffer.set();
- if (this.utils.isEndOfElement())
- {
- return this.keydown.insertDblBreakLine(e);
- }
- return this.keydown.insertBreakLine(e);
- },
- onTab: function(e, key)
- {
- if (!this.opts.tabKey) return true;
- if (this.utils.isEmpty(this.code.get()) && this.opts.tabAsSpaces === false) return true;
- e.preventDefault();
- var node;
- if (this.keydown.pre && !e.shiftKey)
- {
- node = (this.opts.preSpaces) ? document.createTextNode(Array(this.opts.preSpaces + 1).join('\u00a0')) : document.createTextNode('\t');
- this.insert.node(node);
- this.code.sync();
- }
- else if (this.opts.tabAsSpaces !== false)
- {
- node = document.createTextNode(Array(this.opts.tabAsSpaces + 1).join('\u00a0'));
- this.insert.node(node);
- this.code.sync();
- }
- else
- {
- if (e.metaKey && key === 219) this.indent.decrease();
- else if (e.metaKey && key === 221) this.indent.increase();
- else if (!e.shiftKey) this.indent.increase();
- else this.indent.decrease();
- }
- return false;
- },
- replaceDivToBreakLine: function()
- {
- var blockElem = this.selection.getBlock();
- var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
- if ((blockElem.tagName === 'DIV' || blockElem.tagName === 'P') && blockHtml === '' && !$(blockElem).hasClass('redactor-editor'))
- {
- var br = document.createElement('br');
- $(blockElem).replaceWith(br);
- this.caret.setBefore(br);
- this.code.sync();
- return false;
- }
- },
- replaceDivToParagraph: function()
- {
- var blockElem = this.selection.getBlock();
- var blockHtml = blockElem.innerHTML.replace(/<br\s?\/?>/gi, '');
- if (blockElem.tagName === 'DIV' && this.utils.isEmpty(blockHtml) && !$(blockElem).hasClass('redactor-editor'))
- {
- var p = document.createElement('p');
- p.innerHTML = this.opts.invisibleSpace;
- $(blockElem).replaceWith(p);
- this.caret.setStart(p);
- this.code.sync();
- return false;
- }
- else if (this.opts.cleanStyleOnEnter && blockElem.tagName == 'P')
- {
- $(blockElem).removeAttr('class').removeAttr('style');
- }
- },
- insertParagraph: function(e)
- {
- e.preventDefault();
- this.selection.get();
- var p = document.createElement('p');
- p.innerHTML = this.opts.invisibleSpace;
- this.range.deleteContents();
- this.range.insertNode(p);
- this.caret.setStart(p);
- this.code.sync();
- return false;
- },
- exitFromBlockquote: function(e)
- {
- if (!this.utils.isEndOfElement()) return;
- var tmp = $.trim($(this.keydown.block).html());
- if (tmp.search(/(<br\s?\/?>){2}$/i) != -1)
- {
- e.preventDefault();
- if (this.opts.linebreaks)
- {
- var br = document.createElement('br');
- $(this.keydown.blockquote).after(br);
- this.caret.setBefore(br);
- $(this.keydown.block).html(tmp.replace(/<br\s?\/?>$/i, ''));
- }
- else
- {
- var node = $(this.opts.emptyHtml);
- $(this.keydown.blockquote).after(node);
- this.caret.setStart(node);
- }
- return true;
- }
- return;
- },
- insertAfterLastElement: function(element)
- {
- if (!this.utils.isEndOfElement()) return;
- this.buffer.set();
- if (this.opts.linebreaks)
- {
- var contents = $('<div>').append($.trim(this.$editor.html())).contents();
- var last = contents.last()[0];
- if (last.tagName == 'SPAN' && last.innerHTML === '')
- {
- last = contents.prev()[0];
- }
- if (this.utils.getOuterHtml(last) != this.utils.getOuterHtml(element)) return;
- var br = document.createElement('br');
- $(element).after(br);
- this.caret.setAfter(br);
- }
- else
- {
- if (this.$editor.contents().last()[0] !== element) return;
- var node = $(this.opts.emptyHtml);
- $(element).after(node);
- this.caret.setStart(node);
- }
- },
- insertNewLine: function(e)
- {
- e.preventDefault();
- var node = document.createTextNode('\n');
- this.selection.get();
- this.range.deleteContents();
- this.range.insertNode(node);
- this.caret.setAfter(node);
- this.code.sync();
- return false;
- },
- insertBreakLine: function(e)
- {
- return this.keydown.insertBreakLineProcessing(e);
- },
- insertDblBreakLine: function(e)
- {
- return this.keydown.insertBreakLineProcessing(e, true);
- },
- insertBreakLineProcessing: function(e, dbl)
- {
- e.stopPropagation();
- this.selection.get();
- var br1 = document.createElement('br');
- if (this.utils.browser('msie'))
- {
- this.range.collapse(false);
- this.range.setEnd(this.range.endContainer, this.range.endOffset);
- }
- else
- {
- this.range.deleteContents();
- }
- this.range.insertNode(br1);
- // move br outside A tag
- var $parentA = $(br1).parent("a");
- if ($parentA.length > 0)
- {
- $parentA.find(br1).remove();
- $parentA.after(br1);
- }
- if (dbl === true)
- {
- var $next = $(br1).next();
- if ($next.length !== 0 && $next[0].tagName === 'BR' && this.utils.isEndOfEditor())
- {
- this.caret.setAfter(br1);
- this.code.sync();
- return false;
- }
- var br2 = document.createElement('br');
- this.range.insertNode(br2);
- this.caret.setAfter(br2);
- }
- else
- {
- // caret does not move after the br visual
- if (this.utils.browser('msie'))
- {
- var space = document.createElement('span');
- space.innerHTML = '​';
- $(br1).after(space);
- this.range.setStartAfter(space);
- this.range.setEndAfter(space);
- $(space).remove();
- }
- else
- {
- var range = document.createRange();
- range.setStartAfter(br1);
- range.collapse(true);
- var selection = window.getSelection();
- selection.removeAllRanges();
- selection.addRange(range);
- }
- }
- this.code.sync();
- return false;
- },
- removeInvisibleSpace: function()
- {
- var $current = $(this.keydown.current);
- if ($current.text().search(/^\u200B$/g) === 0)
- {
- $current.remove();
- }
- },
- removeEmptyListInTable: function(e)
- {
- var $current = $(this.keydown.current);
- var $parent = $(this.keydown.parent);
- var td = $current.closest('td', this.$editor[0]);
- if (td.length !== 0 && $current.closest('li', this.$editor[0]) && $parent.children('li').length === 1)
- {
- if (!this.utils.isEmpty($current.text())) return;
- e.preventDefault();
- $current.remove();
- $parent.remove();
- this.caret.setStart(td);
- }
- }
- };
- },
- keyup: function()
- {
- return {
- init: function(e)
- {
- if (this.rtePaste) return;
- var key = e.which;
- this.keyup.current = this.selection.getCurrent();
- this.keyup.parent = this.selection.getParent();
- var $parent = this.utils.isRedactorParent($(this.keyup.parent).parent());
- // callback
- var keyupStop = this.core.setCallback('keyup', e);
- if (keyupStop === false)
- {
- e.preventDefault();
- return false;
- }
- // replace to p before / after the table or body
- if (!this.opts.linebreaks && this.keyup.current.nodeType === 3 && this.keyup.current.length <= 1 && (this.keyup.parent === false || this.keyup.parent.tagName == 'BODY'))
- {
- this.keyup.replaceToParagraph();
- }
- // replace div after lists
- if (!this.opts.linebreaks && this.utils.isRedactorParent(this.keyup.current) && this.keyup.current.tagName === 'DIV')
- {
- this.keyup.replaceToParagraph(false);
- }
- if (!this.opts.linebreaks && $(this.keyup.parent).hasClass('redactor-invisible-space') && ($parent === false || $parent[0].tagName == 'BODY'))
- {
- $(this.keyup.parent).contents().unwrap();
- this.keyup.replaceToParagraph();
- }
- // linkify
- if (this.linkify.isEnabled() && this.linkify.isKey(key)) this.linkify.format();
- if (key === this.keyCode.DELETE || key === this.keyCode.BACKSPACE)
- {
- if (this.utils.browser('mozilla'))
- {
- var td = $(this.keydown.current).closest('td', this.$editor[0]);
- if (td.size() !== 0 && td.text() !== '')
- {
- e.preventDefault();
- return false;
- }
- }
- // clear unverified
- this.clean.clearUnverified();
- if (this.observe.image)
- {
- e.preventDefault();
- this.image.hideResize();
- this.buffer.set();
- this.image.remove(this.observe.image);
- this.observe.image = false;
- return false;
- }
- // remove empty paragraphs
- this.$editor.find('p').each($.proxy(function(i, s)
- {
- this.utils.removeEmpty(i, $(s).html());
- }, this));
- // remove invisible space
- if (this.opts.linebreaks && this.keyup.current && this.keyup.current.tagName == 'DIV' && this.utils.isEmpty(this.keyup.current.innerHTML))
- {
- $(this.keyup.current).after(this.selection.getMarkerAsHtml());
- this.selection.restore();
- $(this.keyup.current).remove();
- }
- this.keyup.removeEmptyLists();
- // if empty
- return this.keyup.formatEmpty(e);
- }
- },
- replaceToParagraph: function(clone)
- {
- var $current = $(this.keyup.current);
- var node;
- if (clone === false)
- {
- node = $('<p>').append($current.html());
- }
- else
- {
- node = $('<p>').append($current.clone());
- }
- $current.replaceWith(node);
- var next = $(node).next();
- if (typeof(next[0]) !== 'undefined' && next[0].tagName == 'BR')
- {
- next.remove();
- }
- this.caret.setEnd(node);
- },
- removeEmptyLists: function()
- {
- var removeIt = function()
- {
- var html = $.trim(this.innerHTML).replace(/\/t\/n/g, '');
- if (html === '')
- {
- $(this).remove();
- }
- };
- this.$editor.find('li').each(removeIt);
- this.$editor.find('ul, ol').each(removeIt);
- },
- formatEmpty: function(e)
- {
- var html = $.trim(this.$editor.html());
- if (!this.utils.isEmpty(html)) return;
- e.preventDefault();
- if (this.opts.linebreaks)
- {
- this.$editor.html(this.selection.getMarkerAsHtml());
- this.selection.restore();
- }
- else
- {
- this.$editor.html(this.opts.emptyHtml);
- this.focus.setStart();
- }
- this.code.sync();
- return false;
- }
- };
- },
- lang: function()
- {
- return {
- load: function()
- {
- this.opts.curLang = this.opts.langs[this.opts.lang];
- },
- get: function(name)
- {
- return (typeof this.opts.curLang[name] != 'undefined') ? this.opts.curLang[name] : '';
- }
- };
- },
- line: function()
- {
- return {
- insert: function()
- {
- this.buffer.set();
- var blocks = this.selection.getBlocks();
- if (blocks[0] !== false && this.line.isExceptLastOrFirst(blocks))
- {
- if (!this.utils.browser('msie')) this.$editor.focus();
- return;
- }
- if (this.utils.browser('msie'))
- {
- this.line.insertInIe();
- }
- else
- {
- this.line.insertInOthersBrowsers();
- }
- },
- isExceptLastOrFirst: function(blocks)
- {
- var exceptTags = ['li', 'td', 'th', 'blockquote', 'figcaption', 'pre', 'dl', 'dt', 'dd'];
- var first = blocks[0].tagName.toLowerCase();
- var last = this.selection.getLastBlock();
- last = (typeof last == 'undefined') ? first : last.tagName.toLowerCase();
- var firstFound = $.inArray(first, exceptTags) != -1;
- var lastFound = $.inArray(last, exceptTags) != -1;
- if ((firstFound && lastFound) || firstFound)
- {
- return true;
- }
- },
- insertInIe: function()
- {
- this.utils.saveScroll();
- this.buffer.set();
- this.insert.node(document.createElement('hr'));
- this.utils.restoreScroll();
- this.code.sync();
- },
- insertInOthersBrowsers: function()
- {
- this.buffer.set();
- var extra = '<p id="redactor-insert-line"><br /></p>';
- if (this.opts.linebreaks) extra = '<br id="redactor-insert-line">';
- document.execCommand('insertHtml', false, '<hr>' + extra);
- this.line.setFocus();
- this.code.sync();
- },
- setFocus: function()
- {
- var node = this.$editor.find('#redactor-insert-line');
- var next = $(node).next()[0];
- var target = next;
- if (this.utils.browser('mozilla') && next && next.innerHTML === '')
- {
- target = $(next).next()[0];
- $(next).remove();
- }
- if (target)
- {
- node.remove();
- if (!this.opts.linebreaks)
- {
- this.$editor.focus();
- this.line.setStart(target);
- }
- }
- else
- {
- node.removeAttr('id');
- this.line.setStart(node[0]);
- }
- },
- setStart: function(node)
- {
- if (typeof node === 'undefined') return;
- var textNode = document.createTextNode('\u200B');
- this.selection.get();
- this.range.setStart(node, 0);
- this.range.insertNode(textNode);
- this.range.collapse(true);
- this.selection.addRange();
- }
- };
- },
- link: function()
- {
- return {
- show: function(e)
- {
- if (typeof e != 'undefined' && e.preventDefault) e.preventDefault();
- if (!this.observe.isCurrent('a'))
- {
- this.modal.load('link', this.lang.get('link_insert'), 600);
- }
- else
- {
- this.modal.load('link', this.lang.get('link_edit'), 600);
- }
- this.modal.createCancelButton();
- var buttonText = !this.observe.isCurrent('a') ? this.lang.get('insert') : this.lang.get('edit');
- this.link.buttonInsert = this.modal.createActionButton(buttonText);
- this.selection.get();
- this.link.getData();
- this.link.cleanUrl();
- if (this.link.target == '_blank') $('#redactor-link-blank').prop('checked', true);
- this.link.$inputUrl = $('#redactor-link-url');
- this.link.$inputText = $('#redactor-link-url-text');
- this.link.$inputText.val(this.link.text);
- this.link.$inputUrl.val(this.link.url);
- this.link.buttonInsert.on('click', $.proxy(this.link.insert, this));
- // hide link's tooltip
- $('.redactor-link-tooltip').remove();
- // show modal
- this.selection.save();
- this.modal.show();
- this.link.$inputUrl.focus();
- },
- cleanUrl: function()
- {
- var thref = self.location.href.replace(/\/$/i, '');
- if (typeof this.link.url !== "undefined")
- {
- this.link.url = this.link.url.replace(thref, '');
- this.link.url = this.link.url.replace(/^\/#/, '#');
- this.link.url = this.link.url.replace('mailto:', '');
- // remove host from href
- if (!this.opts.linkProtocol)
- {
- var re = new RegExp('^(http|ftp|https)://' + self.location.host, 'i');
- this.link.url = this.link.url.replace(re, '');
- }
- }
- },
- getData: function()
- {
- this.link.$node = false;
- var $el = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
- if ($el.length !== 0 && $el[0].tagName === 'A')
- {
- this.link.$node = $el;
- this.link.url = $el.attr('href');
- this.link.text = $el.text();
- this.link.target = $el.attr('target');
- }
- else
- {
- this.link.text = this.sel.toString();
- this.link.url = '';
- this.link.target = '';
- }
- },
- insert: function()
- {
- this.placeholder.remove();
- var target = '';
- var link = this.link.$inputUrl.val();
- var text = this.link.$inputText.val().replace(/(<([^>]+)>)/ig,"");
- if ($.trim(link) === '')
- {
- this.link.$inputUrl.addClass('redactor-input-error').on('keyup', function()
- {
- $(this).removeClass('redactor-input-error');
- $(this).off('keyup');
- });
- return;
- }
- // mailto
- if (link.search('@') != -1 && /(http|ftp|https):\/\//i.test(link) === false)
- {
- link = link.replace('mailto:', '');
- link = 'mailto:' + link;
- }
- // url, not anchor
- else if (link.search('#') !== 0)
- {
- if ($('#redactor-link-blank').prop('checked'))
- {
- target = '_blank';
- }
- // test url (add protocol)
- var pattern = '((xn--)?[a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}';
- var re = new RegExp('^(http|ftp|https)://' + pattern, 'i');
- var re2 = new RegExp('^' + pattern, 'i');
- var re3 = new RegExp('\.(html|php)$', 'i');
- if (link.search(re) == -1 && link.search(re3) == -1 && link.search(re2) === 0 && this.opts.linkProtocol)
- {
- link = this.opts.linkProtocol + '://' + link;
- }
- }
- this.link.set(text, link, target);
- this.modal.close();
- },
- set: function(text, link, target)
- {
- text = $.trim(text.replace(/<|>/g, ''));
- this.selection.restore();
- var blocks = this.selection.getBlocks();
- if (text === '' && link === '') return;
- if (text === '' && link !== '') text = link;
- if (this.link.$node)
- {
- this.buffer.set();
- var $link = this.link.$node,
- $el = $link.children();
- if ($el.length > 0)
- {
- while ($el.length)
- {
- $el = $el.children();
- }
- $el = $el.end();
- }
- else
- {
- $el = $link;
- }
- $link.attr('href', link);
- $el.text(text);
- if (target !== '')
- {
- $link.attr('target', target);
- }
- else
- {
- $link.removeAttr('target');
- }
- this.selection.selectElement($link);
- this.code.sync();
- }
- else
- {
- if (this.utils.browser('mozilla') && this.link.text === '')
- {
- var $a = $('<a />').attr('href', link).text(text);
- if (target !== '') $a.attr('target', target);
- $a = $(this.insert.node($a));
- if (this.opts.linebreaks)
- {
- $a.after(' ');
- }
- this.selection.selectElement($a);
- }
- else
- {
- var $a;
- if (this.utils.browser('msie'))
- {
- $a = $('<a href="' + link + '">').text(text);
- if (target !== '') $a.attr('target', target);
- $a = $(this.insert.node($a));
- if (this.selection.getText().match(/\s$/))
- {
- $a.after(" ");
- }
- this.selection.selectElement($a);
- }
- else
- {
- document.execCommand('createLink', false, link);
- $a = $(this.selection.getCurrent()).closest('a', this.$editor[0]);
- if (this.utils.browser('mozilla'))
- {
- $a = $('a[_moz_dirty=""]');
- }
- if (target !== '') $a.attr('target', target);
- $a.removeAttr('style').removeAttr('_moz_dirty');
- if (this.selection.getText().match(/\s$/))
- {
- $a.after(" ");
- }
- if (this.link.text !== '' || this.link.text != text)
- {
- if (!this.opts.linebreaks && blocks && blocks.length <= 1)
- {
- $a.text(text);
- }
- else if (this.opts.linebreaks)
- {
- $a.text(text);
- }
- this.selection.selectElement($a);
- }
- }
- }
- this.code.sync();
- this.core.setCallback('insertedLink', $a);
- }
- // link tooltip
- setTimeout($.proxy(function()
- {
- this.observe.links();
- }, this), 5);
- },
- unlink: function(e)
- {
- if (typeof e != 'undefined' && e.preventDefault)
- {
- e.preventDefault();
- }
- var nodes = this.selection.getNodes();
- if (!nodes) return;
- this.buffer.set();
- var len = nodes.length;
- var links = [];
- for (var i = 0; i < len; i++)
- {
- if (nodes[i].tagName === 'A')
- {
- links.push(nodes[i]);
- }
- var $node = $(nodes[i]).closest('a', this.$editor[0]);
- $node.replaceWith($node.contents());
- }
- this.core.setCallback('deletedLink', links);
- // hide link's tooltip
- $('.redactor-link-tooltip').remove();
- this.code.sync();
- },
- toggleClass: function(className)
- {
- this.link.setClass(className, 'toggleClass');
- },
- addClass: function(className)
- {
- this.link.setClass(className, 'addClass');
- },
- removeClass: function(className)
- {
- this.link.setClass(className, 'removeClass');
- },
- setClass: function(className, func)
- {
- var links = this.selection.getInlinesTags(['a']);
- if (links === false) return;
- $.each(links, function()
- {
- $(this)[func](className);
- });
- }
- };
- },
- linkify: function()
- {
- return {
- isKey: function(key)
- {
- return key == this.keyCode.ENTER || key == this.keyCode.SPACE;
- },
- isEnabled: function()
- {
- return this.opts.convertLinks && (this.opts.convertUrlLinks || this.opts.convertImageLinks || this.opts.convertVideoLinks) && !this.utils.isCurrentOrParent('PRE');
- },
- format: function()
- {
- var linkify = this.linkify,
- opts = this.opts;
- this.$editor
- .find(":not(iframe,img,a,pre)")
- .addBack()
- .contents()
- .filter(function()
- {
- return this.nodeType === 3 && $.trim(this.nodeValue) != "" && !$(this).parent().is("pre") && (this.nodeValue.match(opts.linkify.regexps.youtube) || this.nodeValue.match(opts.linkify.regexps.vimeo) || this.nodeValue.match(opts.linkify.regexps.image) || this.nodeValue.match(opts.linkify.regexps.url));
- })
- .each(function()
- {
- var text = $(this).text(),
- html = text;
- if (opts.convertVideoLinks && (html.match(opts.linkify.regexps.youtube) || html.match(opts.linkify.regexps.vimeo)) )
- {
- html = linkify.convertVideoLinks(html);
- }
- else if (opts.convertImageLinks && html.match(opts.linkify.regexps.image))
- {
- html = linkify.convertImages(html);
- }
- else if (opts.convertUrlLinks)
- {
- html = linkify.convertLinks(html);
- }
- $(this).before(text.replace(text, html))
- .remove();
- });
- var objects = this.$editor.find('.redactor-linkify-object').each(function()
- {
- var $el = $(this);
- $el.removeClass('redactor-linkify-object');
- if ($el.attr('class') === '') $el.removeAttr('class');
- return $el[0];
- });
- // callback
- setTimeout($.proxy(function()
- {
- this.observe.load();
- this.core.setCallback('linkify', objects);
- }, this), 100);
- // sync
- this.code.sync();
- },
- convertVideoLinks: function(html)
- {
- var iframeStart = '<iframe class="redactor-linkify-object" width="500" height="281" src="',
- iframeEnd = '" frameborder="0" allowfullscreen></iframe>';
- if (html.match(this.opts.linkify.regexps.youtube))
- {
- html = html.replace(this.opts.linkify.regexps.youtube, iframeStart + '//www.youtube.com/embed/$1' + iframeEnd);
- }
- if (html.match(this.opts.linkify.regexps.vimeo))
- {
- html = html.replace(this.opts.linkify.regexps.vimeo, iframeStart + '//player.vimeo.com/video/$2' + iframeEnd);
- }
- return html;
- },
- convertImages: function(html)
- {
- var matches = html.match(this.opts.linkify.regexps.image);
- if (matches)
- {
- html = html.replace(html, '<img src="' + matches + '" class="redactor-linkify-object" />');
- if (this.opts.linebreaks)
- {
- if (!this.utils.isEmpty(this.code.get()))
- {
- html = '<br>' + html;
- }
- }
- html += '<br>';
- }
- return html;
- },
- convertLinks: function(html)
- {
- var matches = html.match(this.opts.linkify.regexps.url);
- if (matches)
- {
- matches = $.grep(matches, function(v, k) { return $.inArray(v, matches) === k; });
- var length = matches.length;
- for (var i = 0; i < length; i++)
- {
- var href = matches[i],
- text = href,
- linkProtocol = this.opts.linkProtocol + '://';
- if (href.match(/(https?|ftp):\/\//i) !== null)
- {
- linkProtocol = "";
- }
- if (text.length > this.opts.linkSize)
- {
- text = text.substring(0, this.opts.linkSize) + '...';
- }
- if (text.search('%') === -1)
- {
- text = decodeURIComponent(text);
- }
- var regexB = "\\b";
- if ($.inArray(href.slice(-1), ["/", "&", "="]) != -1)
- {
- regexB = "";
- }
- // escaping url
- var regexp = new RegExp('(' + href.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + regexB + ')', 'g');
- html = html.replace(regexp, '<a href="' + linkProtocol + $.trim(href) + '" class="redactor-linkify-object">' + $.trim(text) + '</a>');
- }
- }
- return html;
- }
- };
- },
- list: function()
- {
- return {
- toggle: function(cmd)
- {
- this.placeholder.remove();
- if (!this.utils.browser('msie') && !this.opts.linebreaks)
- {
- this.$editor.focus();
- }
- this.buffer.set();
- this.selection.save();
- var parent = this.selection.getParent();
- var $list = $(parent).closest('ol, ul', this.$editor[0]);
- if (!this.utils.isRedactorParent($list) && $list.length !== 0)
- {
- $list = false;
- }
- var isUnorderedCmdOrdered, isOrderedCmdUnordered;
- var remove = false;
- if ($list && $list.length)
- {
- remove = true;
- var listTag = $list[0].tagName;
- isUnorderedCmdOrdered = (cmd === 'orderedlist' && listTag === 'UL');
- isOrderedCmdUnordered = (cmd === 'unorderedlist' && listTag === 'OL');
- }
- if (isUnorderedCmdOrdered)
- {
- this.utils.replaceToTag($list, 'ol');
- }
- else if (isOrderedCmdUnordered)
- {
- this.utils.replaceToTag($list, 'ul');
- }
- else
- {
- if (remove)
- {
- this.list.remove(cmd, $list);
- }
- else
- {
- this.list.insert(cmd);
- }
- }
- this.selection.restore();
- this.code.sync();
- },
- insert: function(cmd)
- {
- var current = this.selection.getCurrent();
- var $td = $(current).closest('td, th', this.$editor[0]);
- if (this.utils.browser('msie') && this.opts.linebreaks)
- {
- this.list.insertInIe(cmd);
- }
- else
- {
- document.execCommand('insert' + cmd);
- }
- var parent = this.selection.getParent();
- var $list = $(parent).closest('ol, ul', this.$editor[0]);
- if ($td.length !== 0)
- {
- var newTd = $td.clone();
- $td.after(newTd).remove('');
- }
- if (this.utils.isEmpty($list.find('li').text()))
- {
- var $children = $list.children('li');
- $children.find('br').remove();
- $children.append(this.selection.getMarkerAsHtml());
- if (this.opts.linebreaks && this.utils.browser('mozilla') && $children.size() == 2 && this.utils.isEmpty($children.eq(1).text()))
- {
- $children.eq(1).remove();
- }
- }
- if ($list.length)
- {
- // remove block-element list wrapper
- var $listParent = $list.parent();
- if (this.utils.isRedactorParent($listParent) && $listParent[0].tagName != 'LI' && this.utils.isBlock($listParent[0]))
- {
- $listParent.replaceWith($listParent.contents());
- }
- }
- if (!this.utils.browser('msie'))
- {
- this.$editor.focus();
- }
- this.clean.clearUnverified();
- },
- insertInIe: function(cmd)
- {
- var wrapper = this.selection.wrap('div');
- var wrapperHtml = $(wrapper).html();
- var tmpList = (cmd == 'orderedlist') ? $('<ol>') : $('<ul>');
- var tmpLi = $('<li>');
- if ($.trim(wrapperHtml) === '')
- {
- tmpLi.append(this.selection.getMarkerAsHtml());
- tmpList.append(tmpLi);
- this.$editor.find('#selection-marker-1').replaceWith(tmpList);
- }
- else
- {
- var items = wrapperHtml.split(/<br\s?\/?>/gi);
- if (items)
- {
- for (var i = 0; i < items.length; i++)
- {
- if ($.trim(items[i]) !== '')
- {
- tmpList.append($('<li>').html(items[i]));
- }
- }
- }
- else
- {
- tmpLi.append(wrapperHtml);
- tmpList.append(tmpLi);
- }
- $(wrapper).replaceWith(tmpList);
- }
- },
- remove: function(cmd, $list)
- {
- if ($.inArray('ul', this.selection.getBlocks())) cmd = 'unorderedlist';
- document.execCommand('insert' + cmd);
- var $current = $(this.selection.getCurrent());
- this.indent.fixEmptyIndent();
- if (!this.opts.linebreaks && $current.closest('li, th, td', this.$editor[0]).length === 0)
- {
- document.execCommand('formatblock', false, 'p');
- this.$editor.find('ul, ol, blockquote').each($.proxy(this.utils.removeEmpty, this));
- }
- var $table = $(this.selection.getCurrent()).closest('table', this.$editor[0]);
- var $prev = $table.prev();
- if (!this.opts.linebreaks && $table.length !== 0 && $prev.length !== 0 && $prev[0].tagName == 'BR')
- {
- $prev.remove();
- }
- this.clean.clearUnverified();
- }
- };
- },
- modal: function()
- {
- return {
- callbacks: {},
- loadTemplates: function()
- {
- this.opts.modal = {
- imageEdit: String()
- + '<section id="redactor-modal-image-edit">'
- + '<label>' + this.lang.get('title') + '</label>'
- + '<input type="text" id="redactor-image-title" />'
- + '<label class="redactor-image-link-option">' + this.lang.get('link') + '</label>'
- + '<input type="text" id="redactor-image-link" class="redactor-image-link-option" aria-label="' + this.lang.get('link') + '" />'
- + '<label class="redactor-image-link-option"><input type="checkbox" id="redactor-image-link-blank" aria-label="' + this.lang.get('link_new_tab') + '"> ' + this.lang.get('link_new_tab') + '</label>'
- + '<label class="redactor-image-position-option">' + this.lang.get('image_position') + '</label>'
- + '<select class="redactor-image-position-option" id="redactor-image-align" aria-label="' + this.lang.get('image_position') + '">'
- + '<option value="none">' + this.lang.get('none') + '</option>'
- + '<option value="left">' + this.lang.get('left') + '</option>'
- + '<option value="center">' + this.lang.get('center') + '</option>'
- + '<option value="right">' + this.lang.get('right') + '</option>'
- + '</select>'
- + '</section>',
- image: String()
- + '<section id="redactor-modal-image-insert">'
- + '<div id="redactor-modal-image-droparea"></div>'
- + '</section>',
- file: String()
- + '<section id="redactor-modal-file-insert">'
- + '<div id="redactor-modal-file-upload-box">'
- + '<label>' + this.lang.get('filename') + '</label>'
- + '<input type="text" id="redactor-filename" aria-label="' + this.lang.get('filename') + '" /><br><br>'
- + '<div id="redactor-modal-file-upload"></div>'
- + '</div>'
- + '</section>',
- link: String()
- + '<section id="redactor-modal-link-insert">'
- + '<label>URL</label>'
- + '<input type="url" id="redactor-link-url" aria-label="URL" />'
- + '<label>' + this.lang.get('text') + '</label>'
- + '<input type="text" id="redactor-link-url-text" aria-label="' + this.lang.get('text') + '" />'
- + '<label><input type="checkbox" id="redactor-link-blank"> ' + this.lang.get('link_new_tab') + '</label>'
- + '</section>'
- };
- $.extend(this.opts, this.opts.modal);
- },
- addCallback: function(name, callback)
- {
- this.modal.callbacks[name] = callback;
- },
- createTabber: function($modal)
- {
- this.modal.$tabber = $('<div>').attr('id', 'redactor-modal-tabber');
- $modal.prepend(this.modal.$tabber);
- },
- addTab: function(id, name, active)
- {
- var $tab = $('<a href="#" rel="tab' + id + '">').text(name);
- if (active)
- {
- $tab.addClass('active');
- }
- var self = this;
- $tab.on('click', function(e)
- {
- e.preventDefault();
- $('.redactor-tab').hide();
- $('.redactor-' + $(this).attr('rel')).show();
- self.modal.$tabber.find('a').removeClass('active');
- $(this).addClass('active');
- });
- this.modal.$tabber.append($tab);
- },
- addTemplate: function(name, template)
- {
- this.opts.modal[name] = template;
- },
- getTemplate: function(name)
- {
- return this.opts.modal[name];
- },
- getModal: function()
- {
- return this.$modalBody.find('section');
- },
- load: function(templateName, title, width)
- {
- this.modal.templateName = templateName;
- this.modal.width = width;
- this.modal.build();
- this.modal.enableEvents();
- this.modal.setTitle(title);
- this.modal.setDraggable();
- this.modal.setContent();
- // callbacks
- if (typeof this.modal.callbacks[templateName] != 'undefined')
- {
- this.modal.callbacks[templateName].call(this);
- }
- },
- show: function()
- {
- this.utils.disableBodyScroll();
- if (this.utils.isMobile())
- {
- this.modal.showOnMobile();
- }
- else
- {
- this.modal.showOnDesktop();
- }
- if (this.opts.highContrast)
- {
- this.$modalBox.addClass("redactor-modal-contrast");
- }
- this.$modalOverlay.show();
- this.$modalBox.show();
- this.$modal.attr('tabindex', '-1');
- this.$modal.focus();
- this.modal.setButtonsWidth();
- this.utils.saveScroll();
- // resize
- if (!this.utils.isMobile())
- {
- setTimeout($.proxy(this.modal.showOnDesktop, this), 0);
- $(window).on('resize.redactor-modal', $.proxy(this.modal.resize, this));
- }
- // modal shown callback
- this.core.setCallback('modalOpened', this.modal.templateName, this.$modal);
- // fix bootstrap modal focus
- $(document).off('focusin.modal');
- // enter
- this.$modal.find('input[type=text],input[type=url],input[type=email]').on('keydown.redactor-modal', $.proxy(this.modal.setEnter, this));
- },
- showOnDesktop: function()
- {
- var height = this.$modal.outerHeight();
- var windowHeight = $(window).height();
- var windowWidth = $(window).width();
- if (this.modal.width > windowWidth)
- {
- this.$modal.css({
- width: '96%',
- marginTop: (windowHeight/2 - height/2) + 'px'
- });
- return;
- }
- if (height > windowHeight)
- {
- this.$modal.css({
- width: this.modal.width + 'px',
- marginTop: '20px'
- });
- }
- else
- {
- this.$modal.css({
- width: this.modal.width + 'px',
- marginTop: (windowHeight/2 - height/2) + 'px'
- });
- }
- },
- showOnMobile: function()
- {
- this.$modal.css({
- width: '96%',
- marginTop: '2%'
- });
- },
- resize: function()
- {
- if (this.utils.isMobile())
- {
- this.modal.showOnMobile();
- }
- else
- {
- this.modal.showOnDesktop();
- }
- },
- setTitle: function(title)
- {
- this.$modalHeader.html(title);
- },
- setContent: function()
- {
- this.$modalBody.html(this.modal.getTemplate(this.modal.templateName));
- },
- setDraggable: function()
- {
- if (typeof $.fn.draggable === 'undefined') return;
- this.$modal.draggable({ handle: this.$modalHeader });
- this.$modalHeader.css('cursor', 'move');
- },
- setEnter: function(e)
- {
- if (e.which != 13) return;
- e.preventDefault();
- this.$modal.find('button.redactor-modal-action-btn').click();
- },
- createCancelButton: function()
- {
- var button = $('<button>').addClass('redactor-modal-btn redactor-modal-close-btn').html(this.lang.get('cancel'));
- button.on('click', $.proxy(this.modal.close, this));
- this.$modalFooter.append(button);
- },
- createDeleteButton: function(label)
- {
- return this.modal.createButton(label, 'delete');
- },
- createActionButton: function(label)
- {
- return this.modal.createButton(label, 'action');
- },
- createButton: function(label, className)
- {
- var button = $('<button>').addClass('redactor-modal-btn').addClass('redactor-modal-' + className + '-btn').html(label);
- this.$modalFooter.append(button);
- return button;
- },
- setButtonsWidth: function()
- {
- var buttons = this.$modalFooter.find('button');
- var buttonsSize = buttons.length;
- if (buttonsSize === 0) return;
- buttons.css('width', (100/buttonsSize) + '%');
- },
- build: function()
- {
- this.modal.buildOverlay();
- this.$modalBox = $('<div id="redactor-modal-box"/>').hide();
- this.$modal = $('<div id="redactor-modal" role="dialog" aria-labelledby="redactor-modal-header" />');
- this.$modalHeader = $('<header id="redactor-modal-header"/>');
- this.$modalClose = $('<button type="button" id="redactor-modal-close" tabindex="1" aria-label="Close" />').html('×');
- this.$modalBody = $('<div id="redactor-modal-body" />');
- this.$modalFooter = $('<footer />');
- this.$modal.append(this.$modalHeader);
- this.$modal.append(this.$modalClose);
- this.$modal.append(this.$modalBody);
- this.$modal.append(this.$modalFooter);
- this.$modalBox.append(this.$modal);
- this.$modalBox.appendTo(document.body);
- },
- buildOverlay: function()
- {
- this.$modalOverlay = $('<div id="redactor-modal-overlay">').hide();
- $('body').prepend(this.$modalOverlay);
- },
- enableEvents: function()
- {
- this.$modalClose.on('click.redactor-modal', $.proxy(this.modal.close, this));
- $(document).on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
- this.$editor.on('keyup.redactor-modal', $.proxy(this.modal.closeHandler, this));
- this.$modalBox.on('click.redactor-modal', $.proxy(this.modal.close, this));
- },
- disableEvents: function()
- {
- this.$modalClose.off('click.redactor-modal');
- $(document).off('keyup.redactor-modal');
- this.$editor.off('keyup.redactor-modal');
- this.$modalBox.off('click.redactor-modal');
- $(window).off('resize.redactor-modal');
- },
- closeHandler: function(e)
- {
- if (e.which != this.keyCode.ESC) return;
- this.modal.close(false);
- },
- close: function(e)
- {
- if (e)
- {
- if (!$(e.target).hasClass('redactor-modal-close-btn') && e.target != this.$modalClose[0] && e.target != this.$modalBox[0])
- {
- return;
- }
- e.preventDefault();
- }
- if (!this.$modalBox) return;
- this.modal.disableEvents();
- this.utils.enableBodyScroll();
- this.$modalOverlay.remove();
- this.$modalBox.fadeOut('fast', $.proxy(function()
- {
- this.$modalBox.remove();
- setTimeout($.proxy(this.utils.restoreScroll, this), 0);
- if (e !== undefined) this.selection.restore();
- $(document.body).css('overflow', this.modal.bodyOveflow);
- this.core.setCallback('modalClosed', this.modal.templateName);
- }, this));
- }
- };
- },
- observe: function()
- {
- return {
- load: function()
- {
- if (typeof this.opts.destroyed != "undefined") return;
- if (this.utils.browser('msie'))
- {
- var self = this;
- this.$editor.find('pre, code').on('mouseover',function()
- {
- self.$editor.attr('contenteditable', false);
- $(this).attr('contenteditable', true);
- }).on('mouseout',function()
- {
- self.$editor.attr('contenteditable', true);
- $(this).removeAttr('contenteditable');
- });
- }
- this.observe.images();
- this.observe.links();
- },
- toolbar: function(e, btnName)
- {
- this.observe.buttons(e, btnName);
- this.observe.dropdowns();
- },
- isCurrent: function($el, $current)
- {
- if (typeof $current == 'undefined')
- {
- var $current = $(this.selection.getCurrent());
- }
- return $current.is($el) || $current.parents($el).length > 0;
- },
- dropdowns: function()
- {
- var $current = $(this.selection.getCurrent());
- $.each(this.opts.observe.dropdowns, $.proxy(function(key, value)
- {
- var observe = value.observe,
- element = observe.element,
- $item = value.item,
- inValues = typeof observe['in'] != 'undefined' ? observe['in'] : false,
- outValues = typeof observe['out'] != 'undefined' ? observe['out'] : false;
- if ($current.closest(element).size() > 0)
- {
- this.observe.setDropdownProperties($item, inValues, outValues);
- }
- else
- {
- this.observe.setDropdownProperties($item, outValues, inValues);
- }
- }, this));
- },
- setDropdownProperties: function($item, addProperties, deleteProperties)
- {
- if (deleteProperties && typeof deleteProperties['attr'] != 'undefined')
- {
- this.observe.setDropdownAttr($item, deleteProperties.attr, true);
- }
- if (typeof addProperties['attr'] != 'undefined')
- {
- this.observe.setDropdownAttr($item, addProperties.attr);
- }
- if (typeof addProperties['title'] != 'undefined')
- {
- $item.text(addProperties['title']);
- }
- },
- setDropdownAttr: function($item, properties, isDelete)
- {
- $.each(properties, function(key, value)
- {
- if (key == 'class')
- {
- if (!isDelete)
- {
- $item.addClass(value);
- }
- else
- {
- $item.removeClass(value);
- }
- }
- else
- {
- if (!isDelete)
- {
- $item.attr(key, value);
- }
- else
- {
- $item.removeAttr(key);
- }
- }
- });
- },
- addDropdown: function($item, btnName, btnObject)
- {
- if (typeof btnObject.observe == "undefined") return;
- btnObject.item = $item;
- this.opts.observe.dropdowns.push(btnObject);
- },
- buttons: function(e, btnName)
- {
- var current = this.selection.getCurrent();
- var parent = this.selection.getParent();
- if (e !== false)
- {
- this.button.setInactiveAll();
- }
- else
- {
- this.button.setInactiveAll(btnName);
- }
- if (e === false && btnName !== 'html')
- {
- if ($.inArray(btnName, this.opts.activeButtons) != -1) this.button.toggleActive(btnName);
- return;
- }
- //var linkButtonName = (this.utils.isCurrentOrParent('A')) ? this.lang.get('link_edit') : this.lang.get('link_insert');
- //$('body').find('a.redactor-dropdown-link').text(linkButtonName);
- $.each(this.opts.activeButtonsStates, $.proxy(function(key, value)
- {
- var parentEl = $(parent).closest(key, this.$editor[0]);
- var currentEl = $(current).closest(key, this.$editor[0]);
- if (parentEl.length !== 0 && !this.utils.isRedactorParent(parentEl)) return;
- if (!this.utils.isRedactorParent(currentEl)) return;
- if (parentEl.length !== 0 || currentEl.closest(key, this.$editor[0]).length !== 0)
- {
- this.button.setActive(value);
- }
- }, this));
- var $parent = $(parent).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
- if (this.utils.isRedactorParent(parent) && $parent.length)
- {
- var align = ($parent.css('text-align') === '') ? 'left' : $parent.css('text-align');
- this.button.setActive('align' + align);
- }
- },
- addButton: function(tag, btnName)
- {
- this.opts.activeButtons.push(btnName);
- this.opts.activeButtonsStates[tag] = btnName;
- },
- images: function()
- {
- this.$editor.find('img').each($.proxy(function(i, img)
- {
- var $img = $(img);
- // IE fix (when we clicked on an image and then press backspace IE does goes to image's url)
- $img.closest('a', this.$editor[0]).on('click', function(e) { e.preventDefault(); });
- if (this.utils.browser('msie')) $img.attr('unselectable', 'on');
- this.image.setEditable($img);
- }, this));
- $(document).on('click.redactor-image-delete.' + this.uuid, $.proxy(function(e)
- {
- this.observe.image = false;
- if (e.target.tagName == 'IMG' && this.utils.isRedactorParent(e.target))
- {
- this.observe.image = (this.observe.image && this.observe.image == e.target) ? false : e.target;
- }
- }, this));
- },
- links: function()
- {
- if (!this.opts.linkTooltip) return;
- this.$editor.find('a').on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.showTooltip, this));
- this.$editor.on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
- $(document).on('touchstart.redactor.' + this.uuid + ' click.redactor.' + this.uuid, $.proxy(this.observe.closeTooltip, this));
- },
- getTooltipPosition: function($link)
- {
- return $link.offset();
- },
- showTooltip: function(e)
- {
- var $el = $(e.target);
- if ($el[0].tagName == 'IMG')
- return;
- if ($el[0].tagName !== 'A')
- $el = $el.closest('a', this.$editor[0]);
- if ($el[0].tagName !== 'A')
- return;
- var $link = $el;
- var pos = this.observe.getTooltipPosition($link);
- var tooltip = $('<span class="redactor-link-tooltip"></span>');
- var href = $link.attr('href');
- if (href === undefined)
- {
- href = '';
- }
- if (href.length > 24) href = href.substring(0, 24) + '...';
- var aLink = $('<a href="' + $link.attr('href') + '" target="_blank" />').html(href).addClass('redactor-link-tooltip-action');
- var aEdit = $('<a href="#" />').html(this.lang.get('edit')).on('click', $.proxy(this.link.show, this)).addClass('redactor-link-tooltip-action');
- var aUnlink = $('<a href="#" />').html(this.lang.get('unlink')).on('click', $.proxy(this.link.unlink, this)).addClass('redactor-link-tooltip-action');
- tooltip.append(aLink).append(' | ').append(aEdit).append(' | ').append(aUnlink);
- tooltip.css({
- top: (pos.top + parseInt($link.css('line-height'), 10)) + 'px',
- left: pos.left + 'px'
- });
- $('.redactor-link-tooltip').remove();
- $('body').append(tooltip);
- },
- closeTooltip: function(e)
- {
- e = e.originalEvent || e;
- var target = e.target;
- var $parent = $(target).closest('a', this.$editor[0]);
- if ($parent.length !== 0 && $parent[0].tagName === 'A' && target.tagName !== 'A')
- {
- return;
- }
- else if ((target.tagName === 'A' && this.utils.isRedactorParent(target)) || $(target).hasClass('redactor-link-tooltip-action'))
- {
- return;
- }
- $('.redactor-link-tooltip').remove();
- }
- };
- },
- paragraphize: function()
- {
- return {
- load: function(html)
- {
- if (this.opts.linebreaks) return html;
- if (html === '' || html === '<p></p>') return this.opts.emptyHtml;
- html = html + "\n";
- this.paragraphize.safes = [];
- this.paragraphize.z = 0;
- html = html.replace(/(<br\s?\/?>){1,}\n?<\/blockquote>/gi, '</blockquote>');
- html = this.paragraphize.getSafes(html);
- html = this.paragraphize.getSafesComments(html);
- html = this.paragraphize.replaceBreaksToNewLines(html);
- html = this.paragraphize.replaceBreaksToParagraphs(html);
- html = this.paragraphize.clear(html);
- html = this.paragraphize.restoreSafes(html);
- html = html.replace(new RegExp('<br\\s?/?>\n?<(' + this.opts.paragraphizeBlocks.join('|') + ')(.*?[^>])>', 'gi'), '<p><br /></p>\n<$1$2>');
- return $.trim(html);
- },
- getSafes: function(html)
- {
- var $div = $('<div />').append(html);
- // remove paragraphs in blockquotes
- $div.find('blockquote p').replaceWith(function()
- {
- return $(this).append('<br />').contents();
- });
- html = $div.html();
- $div.find(this.opts.paragraphizeBlocks.join(', ')).each($.proxy(function(i,s)
- {
- this.paragraphize.z++;
- this.paragraphize.safes[this.paragraphize.z] = s.outerHTML;
- html = html.replace(s.outerHTML, '\n{replace' + this.paragraphize.z + '}');
- }, this));
- return html;
- },
- getSafesComments: function(html)
- {
- var commentsMatches = html.match(/<!--([\w\W]*?)-->/gi);
- if (!commentsMatches) return html;
- $.each(commentsMatches, $.proxy(function(i,s)
- {
- this.paragraphize.z++;
- this.paragraphize.safes[this.paragraphize.z] = s;
- html = html.replace(s, '\n{replace' + this.paragraphize.z + '}');
- }, this));
- return html;
- },
- restoreSafes: function(html)
- {
- $.each(this.paragraphize.safes, function(i,s)
- {
- s = (typeof s !== 'undefined') ? s.replace(/\$/g, '$') : s;
- html = html.replace('{replace' + i + '}', s);
- });
- return html;
- },
- replaceBreaksToParagraphs: function(html)
- {
- var htmls = html.split(new RegExp('\n', 'g'), -1);
- html = '';
- if (htmls)
- {
- var len = htmls.length;
- for (var i = 0; i < len; i++)
- {
- if (!htmls.hasOwnProperty(i)) return;
- if (htmls[i].search('{replace') == -1)
- {
- htmls[i] = htmls[i].replace(/<p>\n\t?<\/p>/gi, '');
- htmls[i] = htmls[i].replace(/<p><\/p>/gi, '');
- if (htmls[i] !== '')
- {
- html += '<p>' + htmls[i].replace(/^\n+|\n+$/g, "") + "</p>";
- }
- }
- else html += htmls[i];
- }
- }
- return html;
- },
- replaceBreaksToNewLines: function(html)
- {
- html = html.replace(/<br \/>\s*<br \/>/gi, "\n\n");
- html = html.replace(/<br\s?\/?>\n?<br\s?\/?>/gi, "\n<br /><br />");
- html = html.replace(new RegExp("\r\n", 'g'), "\n");
- html = html.replace(new RegExp("\r", 'g'), "\n");
- html = html.replace(new RegExp("/\n\n+/"), 'g', "\n\n");
- return html;
- },
- clear: function(html)
- {
- html = html.replace(new RegExp('</blockquote></p>', 'gi'), '</blockquote>');
- html = html.replace(new RegExp('<p></blockquote>', 'gi'), '</blockquote>');
- html = html.replace(new RegExp('<p><blockquote>', 'gi'), '<blockquote>');
- html = html.replace(new RegExp('<blockquote></p>', 'gi'), '<blockquote>');
- html = html.replace(new RegExp('<p><p ', 'gi'), '<p ');
- html = html.replace(new RegExp('<p><p>', 'gi'), '<p>');
- html = html.replace(new RegExp('</p></p>', 'gi'), '</p>');
- html = html.replace(new RegExp('<p>\\s?</p>', 'gi'), '');
- html = html.replace(new RegExp("\n</p>", 'gi'), '</p>');
- html = html.replace(new RegExp('<p>\t?\t?\n?<p>', 'gi'), '<p>');
- html = html.replace(new RegExp('<p>\t*</p>', 'gi'), '');
- return html;
- }
- };
- },
- paste: function()
- {
- return {
- init: function(e)
- {
- if (!this.opts.cleanOnPaste)
- {
- setTimeout($.proxy(this.code.sync, this), 1);
- return;
- }
- this.rtePaste = true;
- this.buffer.set();
- this.selection.save();
- this.utils.saveScroll();
- this.paste.createPasteBox();
- $(window).on('scroll.redactor-freeze', $.proxy(function()
- {
- $(window).scrollTop(this.saveBodyScroll);
- }, this));
- setTimeout($.proxy(function()
- {
- var html = this.$pasteBox.html();
- this.$pasteBox.remove();
- this.selection.restore();
- this.utils.restoreScroll();
- this.paste.insert(html);
- $(window).off('scroll.redactor-freeze');
- if (this.linkify.isEnabled())
- {
- this.linkify.format();
- }
- }, this), 1);
- },
- createPasteBox: function()
- {
- this.$pasteBox = $('<div>').html('').attr('contenteditable', 'true').css({ position: 'fixed', width: 0, top: 0, left: '-9999px' });
- if (this.utils.browser('msie'))
- {
- this.$box.append(this.$pasteBox);
- }
- else
- {
- // bootstrap modal
- var $visibleModals = $('.modal-body:visible');
- if ($visibleModals.length > 0)
- {
- $visibleModals.append(this.$pasteBox);
- }
- else
- {
- $('body').append(this.$pasteBox);
- }
- }
- this.$pasteBox.get(0).focus();
- },
- insert: function(html)
- {
- html = this.core.setCallback('pasteBefore', html);
- // clean
- html = (this.utils.isSelectAll()) ? this.clean.onPaste(html, false) : this.clean.onPaste(html);
- html = this.core.setCallback('paste', html);
- if (this.utils.isSelectAll())
- {
- this.insert.set(html, false);
- }
- else
- {
- this.insert.html(html, false);
- }
- this.utils.disableSelectAll();
- this.rtePaste = false;
- setTimeout($.proxy(this.clean.clearUnverified, this), 10);
- // clean empty spans
- setTimeout($.proxy(function()
- {
- var spans = this.$editor.find('span');
- $.each(spans, function(i,s)
- {
- var html = s.innerHTML.replace(/\u200B/, '');
- if (html === '' && s.attributes.length === 0) $(s).remove();
- });
- }, this), 10);
- }
- };
- },
- placeholder: function()
- {
- return {
- enable: function()
- {
- if (!this.placeholder.is()) return;
- this.$editor.attr('placeholder', this.$element.attr('placeholder'));
- this.placeholder.toggle();
- this.$editor.on('keydown.redactor-placeholder', $.proxy(this.placeholder.toggle, this));
- },
- toggle: function()
- {
- setTimeout($.proxy(function()
- {
- var func = this.utils.isEmpty(this.$editor.html(), false) ? 'addClass' : 'removeClass';
- this.$editor[func]('redactor-placeholder');
- }, this), 5);
- },
- remove: function()
- {
- this.$editor.removeClass('redactor-placeholder');
- },
- is: function()
- {
- if (this.opts.placeholder)
- {
- return this.$element.attr('placeholder', this.opts.placeholder);
- }
- else
- {
- return !(typeof this.$element.attr('placeholder') == 'undefined' || this.$element.attr('placeholder') === '');
- }
- }
- };
- },
- progress: function()
- {
- return {
- show: function()
- {
- $(document.body).append($('<div id="redactor-progress"><span></span></div>'));
- $('#redactor-progress').fadeIn();
- },
- hide: function()
- {
- $('#redactor-progress').fadeOut(1500, function()
- {
- $(this).remove();
- });
- }
- };
- },
- selection: function()
- {
- return {
- get: function()
- {
- this.sel = document.getSelection();
- if (document.getSelection && this.sel.getRangeAt && this.sel.rangeCount)
- {
- this.range = this.sel.getRangeAt(0);
- }
- else
- {
- this.range = document.createRange();
- }
- },
- addRange: function()
- {
- try {
- this.sel.removeAllRanges();
- } catch (e) {}
- this.sel.addRange(this.range);
- },
- getCurrent: function()
- {
- var el = false;
- this.selection.get();
- if (this.sel && this.sel.rangeCount > 0)
- {
- el = this.sel.getRangeAt(0).startContainer;
- }
- return this.utils.isRedactorParent(el);
- },
- getParent: function(elem)
- {
- elem = elem || this.selection.getCurrent();
- if (elem)
- {
- return this.utils.isRedactorParent($(elem).parent()[0]);
- }
- return false;
- },
- getPrev: function()
- {
- return window.getSelection().anchorNode.previousSibling;
- },
- getNext: function()
- {
- return window.getSelection().anchorNode.nextSibling;
- },
- getBlock: function(node)
- {
- node = node || this.selection.getCurrent();
- while (node)
- {
- if (this.utils.isBlockTag(node.tagName))
- {
- return ($(node).hasClass('redactor-editor')) ? false : node;
- }
- node = node.parentNode;
- }
- return false;
- },
- getInlines: function(nodes, tags)
- {
- this.selection.get();
- if (this.range && this.range.collapsed)
- {
- return false;
- }
- var inlines = [];
- nodes = (typeof nodes == 'undefined' || nodes === false) ? this.selection.getNodes() : nodes;
- var inlineTags = this.opts.inlineTags;
- inlineTags.push('span');
- if (typeof tags !== 'undefined')
- {
- for (var i = 0; i < tags.length; i++)
- {
- inlineTags.push(tags[i]);
- }
- }
- $.each(nodes, $.proxy(function(i,node)
- {
- if ($.inArray(node.tagName.toLowerCase(), inlineTags) != -1)
- {
- inlines.push(node);
- }
- }, this));
- return (inlines.length === 0) ? false : inlines;
- },
- getInlinesTags: function(tags)
- {
- this.selection.get();
- if (this.range && this.range.collapsed)
- {
- return false;
- }
- var inlines = [];
- var nodes = this.selection.getNodes();
- $.each(nodes, $.proxy(function(i,node)
- {
- if ($.inArray(node.tagName.toLowerCase(), tags) != -1)
- {
- inlines.push(node);
- }
- }, this));
- return (inlines.length === 0) ? false : inlines;
- },
- getBlocks: function(nodes)
- {
- this.selection.get();
- if (this.range && this.range.collapsed)
- {
- return [this.selection.getBlock()];
- }
- var blocks = [];
- nodes = (typeof nodes == 'undefined') ? this.selection.getNodes() : nodes;
- $.each(nodes, $.proxy(function(i,node)
- {
- if (this.utils.isBlock(node))
- {
- blocks.push(node);
- }
- }, this));
- return (blocks.length === 0) ? [this.selection.getBlock()] : blocks;
- },
- getLastBlock: function()
- {
- return this.selection.lastBlock;
- },
- getNodes: function()
- {
- this.selection.get();
- var startNode = this.selection.getNodesMarker(1);
- var endNode = this.selection.getNodesMarker(2);
- if (this.range.collapsed === false)
- {
- if (window.getSelection) {
- var sel = window.getSelection();
- if (sel.rangeCount > 0) {
- var range = sel.getRangeAt(0);
- var startPointNode = range.startContainer, startOffset = range.startOffset;
- var boundaryRange = range.cloneRange();
- boundaryRange.collapse(false);
- boundaryRange.insertNode(endNode);
- boundaryRange.setStart(startPointNode, startOffset);
- boundaryRange.collapse(true);
- boundaryRange.insertNode(startNode);
- // Reselect the original text
- range.setStartAfter(startNode);
- range.setEndBefore(endNode);
- sel.removeAllRanges();
- sel.addRange(range);
- }
- }
- }
- else
- {
- this.selection.setNodesMarker(this.range, startNode, true);
- endNode = startNode;
- }
- var nodes = [];
- var counter = 0;
- var self = this;
- this.$editor.find('*').each(function()
- {
- if (this == startNode)
- {
- var parent = $(this).parent();
- if (parent.length !== 0 && parent[0].tagName != 'BODY' && self.utils.isRedactorParent(parent[0]))
- {
- nodes.push(parent[0]);
- }
- nodes.push(this);
- counter = 1;
- }
- else
- {
- if (counter > 0)
- {
- nodes.push(this);
- counter = counter + 1;
- }
- }
- if (this == endNode)
- {
- return false;
- }
- });
- var finalNodes = [];
- var len = nodes.length;
- for (var i = 0; i < len; i++)
- {
- if (nodes[i].id != 'nodes-marker-1' && nodes[i].id != 'nodes-marker-2')
- {
- finalNodes.push(nodes[i]);
- }
- }
- this.selection.removeNodesMarkers();
- return finalNodes;
- },
- getNodesMarker: function(num)
- {
- return $('<span id="nodes-marker-' + num + '" class="redactor-nodes-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
- },
- setNodesMarker: function(range, node, type)
- {
- var range = range.cloneRange();
- try {
- range.collapse(type);
- range.insertNode(node);
- }
- catch (e) {}
- },
- removeNodesMarkers: function()
- {
- $(document).find('span.redactor-nodes-marker').remove();
- this.$editor.find('span.redactor-nodes-marker').remove();
- },
- fromPoint: function(start, end)
- {
- this.caret.setOffset(start, end);
- },
- wrap: function(tag)
- {
- this.selection.get();
- if (this.range.collapsed) return false;
- var wrapper = document.createElement(tag);
- wrapper.appendChild(this.range.extractContents());
- this.range.insertNode(wrapper);
- return wrapper;
- },
- selectElement: function(node)
- {
- if (this.utils.browser('mozilla'))
- {
- node = node[0] || node;
- var range = document.createRange();
- range.selectNodeContents(node);
- }
- else
- {
- this.caret.set(node, 0, node, 1);
- }
- },
- selectAll: function()
- {
- this.selection.get();
- this.range.selectNodeContents(this.$editor[0]);
- this.selection.addRange();
- },
- remove: function()
- {
- this.selection.get();
- this.sel.removeAllRanges();
- },
- save: function()
- {
- this.selection.createMarkers();
- },
- createMarkers: function()
- {
- this.selection.get();
- var node1 = this.selection.getMarker(1);
- this.selection.setMarker(this.range, node1, true);
- if (this.range.collapsed === false)
- {
- var node2 = this.selection.getMarker(2);
- this.selection.setMarker(this.range, node2, false);
- }
- this.savedSel = this.$editor.html();
- },
- getMarker: function(num)
- {
- if (typeof num == 'undefined') num = 1;
- return $('<span id="selection-marker-' + num + '" class="redactor-selection-marker" data-verified="redactor">' + this.opts.invisibleSpace + '</span>')[0];
- },
- getMarkerAsHtml: function(num)
- {
- return this.utils.getOuterHtml(this.selection.getMarker(num));
- },
- setMarker: function(range, node, type)
- {
- range = range.cloneRange();
- try {
- range.collapse(type);
- range.insertNode(node);
- }
- catch (e)
- {
- this.focus.setStart();
- }
- },
- restore: function()
- {
- var node1 = this.$editor.find('span#selection-marker-1');
- var node2 = this.$editor.find('span#selection-marker-2');
- if (this.utils.browser('mozilla'))
- {
- this.$editor.focus();
- }
- if (node1.length !== 0 && node2.length !== 0)
- {
- this.caret.set(node1, 0, node2, 0);
- }
- else if (node1.length !== 0)
- {
- this.caret.set(node1, 0, node1, 0);
- }
- else
- {
- this.$editor.focus();
- }
- this.selection.removeMarkers();
- this.savedSel = false;
- },
- removeMarkers: function()
- {
- this.$editor.find('span.redactor-selection-marker').each(function(i,s)
- {
- var text = $(s).text().replace(/\u200B/g, '');
- if (text === '') $(s).remove();
- else $(s).replaceWith(function() { return $(this).contents(); });
- });
- },
- getText: function()
- {
- this.selection.get();
- return this.sel.toString();
- },
- getHtml: function()
- {
- var html = '';
- this.selection.get();
- if (this.sel.rangeCount)
- {
- var container = document.createElement('div');
- var len = this.sel.rangeCount;
- for (var i = 0; i < len; ++i)
- {
- container.appendChild(this.sel.getRangeAt(i).cloneContents());
- }
- html = container.innerHTML;
- }
- return this.clean.onSync(html);
- },
- replaceSelection: function(html)
- {
- this.selection.get();
- this.range.deleteContents();
- var div = document.createElement("div");
- div.innerHTML = html;
- var frag = document.createDocumentFragment(), child;
- while ((child = div.firstChild)) {
- frag.appendChild(child);
- }
- this.range.insertNode(frag);
- },
- replaceWithHtml: function(html)
- {
- html = this.selection.getMarkerAsHtml(1) + html + this.selection.getMarkerAsHtml(2);
- this.selection.get();
- if (window.getSelection && window.getSelection().getRangeAt)
- {
- this.selection.replaceSelection(html);
- }
- else if (document.selection && document.selection.createRange)
- {
- this.range.pasteHTML(html);
- }
- this.selection.restore();
- this.code.sync();
- }
- };
- },
- shortcuts: function()
- {
- return {
- init: function(e, key)
- {
- // disable browser's hot keys for bold and italic
- if (!this.opts.shortcuts)
- {
- if ((e.ctrlKey || e.metaKey) && (key === 66 || key === 73)) e.preventDefault();
- return false;
- }
- $.each(this.opts.shortcuts, $.proxy(function(str, command)
- {
- var keys = str.split(',');
- var len = keys.length;
- for (var i = 0; i < len; i++)
- {
- if (typeof keys[i] === 'string')
- {
- this.shortcuts.handler(e, $.trim(keys[i]), $.proxy(function()
- {
- var func;
- if (command.func.search(/\./) != '-1')
- {
- func = command.func.split('.');
- if (typeof this[func[0]] != 'undefined')
- {
- this[func[0]][func[1]].apply(this, command.params);
- }
- }
- else
- {
- this[command.func].apply(this, command.params);
- }
- }, this));
- }
- }
- }, this));
- },
- handler: function(e, keys, origHandler)
- {
- // based on https://github.com/jeresig/jquery.hotkeys
- var hotkeysSpecialKeys =
- {
- 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
- 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
- 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", 59: ";", 61: "=",
- 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
- 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
- 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
- 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 173: "-", 186: ";", 187: "=",
- 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", 221: "]", 222: "'"
- };
- var hotkeysShiftNums =
- {
- "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
- "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
- ".": ">", "/": "?", "\\": "|"
- };
- keys = keys.toLowerCase().split(" ");
- var special = hotkeysSpecialKeys[e.keyCode],
- character = String.fromCharCode( e.which ).toLowerCase(),
- modif = "", possible = {};
- $.each([ "alt", "ctrl", "meta", "shift"], function(index, specialKey)
- {
- if (e[specialKey + 'Key'] && special !== specialKey)
- {
- modif += specialKey + '+';
- }
- });
- if (special) possible[modif + special] = true;
- if (character)
- {
- possible[modif + character] = true;
- possible[modif + hotkeysShiftNums[character]] = true;
- // "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
- if (modif === "shift+")
- {
- possible[hotkeysShiftNums[character]] = true;
- }
- }
- for (var i = 0, len = keys.length; i < len; i++)
- {
- if (possible[keys[i]])
- {
- e.preventDefault();
- return origHandler.apply(this, arguments);
- }
- }
- }
- };
- },
- tabifier: function()
- {
- return {
- get: function(code)
- {
- if (!this.opts.tabifier) return code;
- // clean setup
- var ownLine = ['area', 'body', 'head', 'hr', 'i?frame', 'link', 'meta', 'noscript', 'style', 'script', 'table', 'tbody', 'thead', 'tfoot'];
- var contOwnLine = ['li', 'dt', 'dt', 'h[1-6]', 'option', 'script'];
- var newLevel = ['p', 'blockquote', 'div', 'dl', 'fieldset', 'form', 'frameset', 'map', 'ol', 'pre', 'select', 'td', 'th', 'tr', 'ul'];
- this.tabifier.lineBefore = new RegExp('^<(/?' + ownLine.join('|/?' ) + '|' + contOwnLine.join('|') + ')[ >]');
- this.tabifier.lineAfter = new RegExp('^<(br|/?' + ownLine.join('|/?' ) + '|/' + contOwnLine.join('|/') + ')[ >]');
- this.tabifier.newLevel = new RegExp('^</?(' + newLevel.join('|' ) + ')[ >]');
- var i = 0,
- codeLength = code.length,
- point = 0,
- start = null,
- end = null,
- tag = '',
- out = '',
- cont = '';
- this.tabifier.cleanlevel = 0;
- for (; i < codeLength; i++)
- {
- point = i;
- // if no more tags, copy and exit
- if (-1 == code.substr(i).indexOf( '<' ))
- {
- out += code.substr(i);
- return this.tabifier.finish(out);
- }
- // copy verbatim until a tag
- while (point < codeLength && code.charAt(point) != '<')
- {
- point++;
- }
- if (i != point)
- {
- cont = code.substr(i, point - i);
- if (!cont.match(/^\s{2,}$/g))
- {
- if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
- else if ('\n' == cont.charAt(0))
- {
- out += '\n' + this.tabifier.getTabs();
- cont = cont.replace(/^\s+/, '');
- }
- out += cont;
- }
- if (cont.match(/\n/)) out += '\n' + this.tabifier.getTabs();
- }
- start = point;
- // find the end of the tag
- while (point < codeLength && '>' != code.charAt(point))
- {
- point++;
- }
- tag = code.substr(start, point - start);
- i = point;
- var t;
- if ('!--' == tag.substr(1, 3))
- {
- if (!tag.match(/--$/))
- {
- while ('-->' != code.substr(point, 3))
- {
- point++;
- }
- point += 2;
- tag = code.substr(start, point - start);
- i = point;
- }
- if ('\n' != out.charAt(out.length - 1)) out += '\n';
- out += this.tabifier.getTabs();
- out += tag + '>\n';
- }
- else if ('!' == tag[1])
- {
- out = this.tabifier.placeTag(tag + '>', out);
- }
- else if ('?' == tag[1])
- {
- out += tag + '>\n';
- }
- else if (t = tag.match(/^<(script|style|pre)/i))
- {
- t[1] = t[1].toLowerCase();
- tag = this.tabifier.cleanTag(tag);
- out = this.tabifier.placeTag(tag, out);
- end = String(code.substr(i + 1)).toLowerCase().indexOf('</' + t[1]);
- if (end)
- {
- cont = code.substr(i + 1, end);
- i += end;
- out += cont;
- }
- }
- else
- {
- tag = this.tabifier.cleanTag(tag);
- out = this.tabifier.placeTag(tag, out);
- }
- }
- return this.tabifier.finish(out);
- },
- getTabs: function()
- {
- var s = '';
- for ( var j = 0; j < this.tabifier.cleanlevel; j++ )
- {
- s += '\t';
- }
- return s;
- },
- finish: function(code)
- {
- code = code.replace(/\n\s*\n/g, '\n');
- code = code.replace(/^[\s\n]*/, '');
- code = code.replace(/[\s\n]*$/, '');
- code = code.replace(/<script(.*?)>\n<\/script>/gi, '<script$1></script>');
- this.tabifier.cleanlevel = 0;
- return code;
- },
- cleanTag: function (tag)
- {
- var tagout = '';
- tag = tag.replace(/\n/g, ' ');
- tag = tag.replace(/\s{2,}/g, ' ');
- tag = tag.replace(/^\s+|\s+$/g, ' ');
- var suffix = '';
- if (tag.match(/\/$/))
- {
- suffix = '/';
- tag = tag.replace(/\/+$/, '');
- }
- var m;
- while (m = /\s*([^= ]+)(?:=((['"']).*?\3|[^ ]+))?/.exec(tag))
- {
- if (m[2]) tagout += m[1].toLowerCase() + '=' + m[2];
- else if (m[1]) tagout += m[1].toLowerCase();
- tagout += ' ';
- tag = tag.substr(m[0].length);
- }
- return tagout.replace(/\s*$/, '') + suffix + '>';
- },
- placeTag: function (tag, out)
- {
- var nl = tag.match(this.tabifier.newLevel);
- if (tag.match(this.tabifier.lineBefore) || nl)
- {
- out = out.replace(/\s*$/, '');
- out += '\n';
- }
- if (nl && '/' == tag.charAt(1)) this.tabifier.cleanlevel--;
- if ('\n' == out.charAt(out.length - 1)) out += this.tabifier.getTabs();
- if (nl && '/' != tag.charAt(1)) this.tabifier.cleanlevel++;
- out += tag;
- if (tag.match(this.tabifier.lineAfter) || tag.match(this.tabifier.newLevel))
- {
- out = out.replace(/ *$/, '');
- //out += '\n';
- }
- return out;
- }
- };
- },
- tidy: function()
- {
- return {
- setupAllowed: function()
- {
- var index = $.inArray('span', this.opts.removeEmpty);
- if (index !== -1)
- {
- this.opts.removeEmpty.splice(index, 1);
- }
- if (this.opts.allowedTags) this.opts.deniedTags = false;
- if (this.opts.allowedAttr) this.opts.removeAttr = false;
- if (this.opts.linebreaks) return;
- var tags = ['p', 'section'];
- if (this.opts.allowedTags) this.tidy.addToAllowed(tags);
- if (this.opts.deniedTags) this.tidy.removeFromDenied(tags);
- },
- addToAllowed: function(tags)
- {
- var len = tags.length;
- for (var i = 0; i < len; i++)
- {
- if ($.inArray(tags[i], this.opts.allowedTags) == -1)
- {
- this.opts.allowedTags.push(tags[i]);
- }
- }
- },
- removeFromDenied: function(tags)
- {
- var len = tags.length;
- for (var i = 0; i < len; i++)
- {
- var pos = $.inArray(tags[i], this.opts.deniedTags);
- if (pos != -1)
- {
- this.opts.deniedTags.splice(pos, 1);
- }
- }
- },
- load: function(html, options)
- {
- this.tidy.settings = {
- deniedTags: this.opts.deniedTags,
- allowedTags: this.opts.allowedTags,
- removeComments: this.opts.removeComments,
- replaceTags: this.opts.replaceTags,
- replaceStyles: this.opts.replaceStyles,
- removeDataAttr: this.opts.removeDataAttr,
- removeAttr: this.opts.removeAttr,
- allowedAttr: this.opts.allowedAttr,
- removeWithoutAttr: this.opts.removeWithoutAttr,
- removeEmpty: this.opts.removeEmpty
- };
- $.extend(this.tidy.settings, options);
- html = this.tidy.removeComments(html);
- // create container
- this.tidy.$div = $('<div />').append(html);
- // clean
- this.tidy.replaceTags();
- this.tidy.replaceStyles();
- this.tidy.removeTags();
- this.tidy.removeAttr();
- this.tidy.removeEmpty();
- this.tidy.removeParagraphsInLists();
- this.tidy.removeDataAttr();
- this.tidy.removeWithoutAttr();
- html = this.tidy.$div.html();
- this.tidy.$div.remove();
- return html;
- },
- removeComments: function(html)
- {
- if (!this.tidy.settings.removeComments) return html;
- return html.replace(/<!--[\s\S]*?-->/gi, '');
- },
- replaceTags: function(html)
- {
- if (!this.tidy.settings.replaceTags) return html;
- var len = this.tidy.settings.replaceTags.length;
- var replacement = [], rTags = [];
- for (var i = 0; i < len; i++)
- {
- rTags.push(this.tidy.settings.replaceTags[i][1]);
- replacement.push(this.tidy.settings.replaceTags[i][0]);
- }
- $.each(replacement, $.proxy(function(key, value)
- {
- this.tidy.$div.find(value).replaceWith(function()
- {
- return $("<" + rTags[key] + " />", {html: $(this).html()});
- });
- }, this));
- },
- replaceStyles: function()
- {
- if (!this.tidy.settings.replaceStyles) return;
- var len = this.tidy.settings.replaceStyles.length;
- this.tidy.$div.find('span').each($.proxy(function(n,s)
- {
- var $el = $(s);
- var style = $el.attr('style');
- for (var i = 0; i < len; i++)
- {
- if (style && style.match(new RegExp('^' + this.tidy.settings.replaceStyles[i][0], 'i')))
- {
- var tagName = this.tidy.settings.replaceStyles[i][1];
- $el.replaceWith(function()
- {
- var tag = document.createElement(tagName);
- return $(tag).append($(this).contents());
- });
- }
- }
- }, this));
- },
- removeTags: function()
- {
- if (!this.tidy.settings.deniedTags && this.tidy.settings.allowedTags)
- {
- this.tidy.$div.find('*').not(this.tidy.settings.allowedTags.join(',')).each(function(i, s)
- {
- if (s.innerHTML === '') $(s).remove();
- else $(s).contents().unwrap();
- });
- }
- if (this.tidy.settings.deniedTags)
- {
- this.tidy.$div.find(this.tidy.settings.deniedTags.join(',')).each(function(i, s)
- {
- if ($(s).hasClass('redactor-script-tag') || $(s).hasClass('redactor-selection-marker')) return;
- if (s.innerHTML === '') $(s).remove();
- else $(s).contents().unwrap();
- });
- }
- },
- removeAttr: function()
- {
- var len;
- if (!this.tidy.settings.removeAttr && this.tidy.settings.allowedAttr)
- {
- var allowedAttrTags = [], allowedAttrData = [];
- len = this.tidy.settings.allowedAttr.length;
- for (var i = 0; i < len; i++)
- {
- allowedAttrTags.push(this.tidy.settings.allowedAttr[i][0]);
- allowedAttrData.push(this.tidy.settings.allowedAttr[i][1]);
- }
- this.tidy.$div.find('*').each($.proxy(function(n,s)
- {
- var $el = $(s);
- var pos = $.inArray($el[0].tagName.toLowerCase(), allowedAttrTags);
- var attributesRemove = this.tidy.removeAttrGetRemoves(pos, allowedAttrData, $el);
- if (attributesRemove)
- {
- $.each(attributesRemove, function(z,f) {
- $el.removeAttr(f);
- });
- }
- }, this));
- }
- if (this.tidy.settings.removeAttr)
- {
- len = this.tidy.settings.removeAttr.length;
- for (var i = 0; i < len; i++)
- {
- var attrs = this.tidy.settings.removeAttr[i][1];
- if ($.isArray(attrs)) attrs = attrs.join(' ');
- this.tidy.$div.find(this.tidy.settings.removeAttr[i][0]).removeAttr(attrs);
- }
- }
- },
- removeAttrGetRemoves: function(pos, allowed, $el)
- {
- var attributesRemove = [];
- // remove all attrs
- if (pos == -1)
- {
- $.each($el[0].attributes, function(i, item)
- {
- attributesRemove.push(item.name);
- });
- }
- // allow all attrs
- else if (allowed[pos] == '*')
- {
- attributesRemove = [];
- }
- // allow specific attrs
- else
- {
- $.each($el[0].attributes, function(i, item)
- {
- if ($.isArray(allowed[pos]))
- {
- if ($.inArray(item.name, allowed[pos]) == -1)
- {
- attributesRemove.push(item.name);
- }
- }
- else if (allowed[pos] != item.name)
- {
- attributesRemove.push(item.name);
- }
- });
- }
- return attributesRemove;
- },
- removeAttrs: function (el, regex)
- {
- regex = new RegExp(regex, "g");
- return el.each(function()
- {
- var self = $(this);
- var len = this.attributes.length - 1;
- for (var i = len; i >= 0; i--)
- {
- var item = this.attributes[i];
- if (item && item.specified && item.name.search(regex)>=0)
- {
- self.removeAttr(item.name);
- }
- }
- });
- },
- removeEmpty: function()
- {
- if (!this.tidy.settings.removeEmpty) return;
- this.tidy.$div.find(this.tidy.settings.removeEmpty.join(',')).each(function()
- {
- var $el = $(this);
- var text = $el.text();
- text = text.replace(/\u200B/g, '');
- text = text.replace(/ /gi, '');
- text = text.replace(/\s/g, '');
- if (text === '' && $el.children().length === 0)
- {
- $el.remove();
- }
- });
- },
- removeParagraphsInLists: function()
- {
- this.tidy.$div.find('li p').contents().unwrap();
- },
- removeDataAttr: function()
- {
- if (!this.tidy.settings.removeDataAttr) return;
- var tags = this.tidy.settings.removeDataAttr;
- if ($.isArray(this.tidy.settings.removeDataAttr)) tags = this.tidy.settings.removeDataAttr.join(',');
- this.tidy.removeAttrs(this.tidy.$div.find(tags), '^(data-)');
- },
- removeWithoutAttr: function()
- {
- if (!this.tidy.settings.removeWithoutAttr) return;
- this.tidy.$div.find(this.tidy.settings.removeWithoutAttr.join(',')).each(function()
- {
- if (this.attributes.length === 0)
- {
- $(this).contents().unwrap();
- }
- });
- }
- };
- },
- toolbar: function()
- {
- return {
- init: function()
- {
- return {
- html:
- {
- title: this.lang.get('html'),
- func: 'code.toggle'
- },
- formatting:
- {
- title: this.lang.get('formatting'),
- dropdown:
- {
- p:
- {
- title: this.lang.get('paragraph'),
- func: 'block.format'
- },
- blockquote:
- {
- title: this.lang.get('quote'),
- func: 'block.format'
- },
- pre:
- {
- title: this.lang.get('code'),
- func: 'block.format'
- },
- h1:
- {
- title: this.lang.get('header1'),
- func: 'block.format'
- },
- h2:
- {
- title: this.lang.get('header2'),
- func: 'block.format'
- },
- h3:
- {
- title: this.lang.get('header3'),
- func: 'block.format'
- },
- h4:
- {
- title: this.lang.get('header4'),
- func: 'block.format'
- },
- h5:
- {
- title: this.lang.get('header5'),
- func: 'block.format'
- }
- }
- },
- bold:
- {
- title: this.lang.get('bold'),
- func: 'inline.format'
- },
- italic:
- {
- title: this.lang.get('italic'),
- func: 'inline.format'
- },
- deleted:
- {
- title: this.lang.get('deleted'),
- func: 'inline.format'
- },
- underline:
- {
- title: this.lang.get('underline'),
- func: 'inline.format'
- },
- unorderedlist:
- {
- title: '• ' + this.lang.get('unorderedlist'),
- func: 'list.toggle'
- },
- orderedlist:
- {
- title: '1. ' + this.lang.get('orderedlist'),
- func: 'list.toggle'
- },
- outdent:
- {
- title: '< ' + this.lang.get('outdent'),
- func: 'indent.decrease'
- },
- indent:
- {
- title: '> ' + this.lang.get('indent'),
- func: 'indent.increase'
- },
- image:
- {
- title: this.lang.get('image'),
- func: 'image.show'
- },
- file:
- {
- title: this.lang.get('file'),
- func: 'file.show'
- },
- link:
- {
- title: this.lang.get('link'),
- dropdown:
- {
- link:
- {
- title: this.lang.get('link_insert'),
- func: 'link.show',
- observe: {
- element: 'a',
- in: {
- title: this.lang.get('link_edit'),
- },
- out: {
- title: this.lang.get('link_insert')
- }
- }
- },
- unlink:
- {
- title: this.lang.get('unlink'),
- func: 'link.unlink',
- observe: {
- element: 'a',
- out: {
- attr: {
- 'class': 'redactor-dropdown-link-inactive',
- 'aria-disabled': true
- }
- }
- }
- }
- }
- },
- alignment:
- {
- title: this.lang.get('alignment'),
- dropdown:
- {
- left:
- {
- title: this.lang.get('align_left'),
- func: 'alignment.left'
- },
- center:
- {
- title: this.lang.get('align_center'),
- func: 'alignment.center'
- },
- right:
- {
- title: this.lang.get('align_right'),
- func: 'alignment.right'
- },
- justify:
- {
- title: this.lang.get('align_justify'),
- func: 'alignment.justify'
- }
- }
- },
- horizontalrule:
- {
- title: this.lang.get('horizontalrule'),
- func: 'line.insert'
- }
- };
- },
- build: function()
- {
- this.toolbar.hideButtons();
- this.toolbar.hideButtonsOnMobile();
- this.toolbar.isButtonSourceNeeded();
- if (this.opts.buttons.length === 0) return;
- this.$toolbar = this.toolbar.createContainer();
- this.toolbar.setOverflow();
- this.toolbar.append();
- this.toolbar.setFormattingTags();
- this.toolbar.loadButtons();
- this.toolbar.setFixed();
- // buttons response
- if (this.opts.activeButtons)
- {
- this.$editor.on('mouseup.redactor keyup.redactor focus.redactor', $.proxy(this.observe.toolbar, this));
- }
- },
- createContainer: function()
- {
- return $('<ul>').addClass('redactor-toolbar').attr({'id': 'redactor-toolbar-' + this.uuid, 'role': 'toolbar'});
- },
- setFormattingTags: function()
- {
- $.each(this.opts.toolbar.formatting.dropdown, $.proxy(function (i, s)
- {
- if ($.inArray(i, this.opts.formatting) == -1) delete this.opts.toolbar.formatting.dropdown[i];
- }, this));
- },
- loadButtons: function()
- {
- $.each(this.opts.buttons, $.proxy(function(i, btnName)
- {
- if (!this.opts.toolbar[btnName]) return;
- if (btnName === 'file')
- {
- if (this.opts.fileUpload === false) return;
- else if (!this.opts.fileUpload && this.opts.s3 === false) return;
- }
- if (btnName === 'image')
- {
- if (this.opts.imageUpload === false) return;
- else if (!this.opts.imageUpload && this.opts.s3 === false) return;
- }
- var btnObject = this.opts.toolbar[btnName];
- this.$toolbar.append($('<li>').append(this.button.build(btnName, btnObject)));
- }, this));
- },
- append: function()
- {
- if (this.opts.toolbarExternal)
- {
- this.$toolbar.addClass('redactor-toolbar-external');
- $(this.opts.toolbarExternal).html(this.$toolbar);
- }
- else
- {
- this.$box.prepend(this.$toolbar);
- }
- },
- setFixed: function()
- {
- if (!this.utils.isDesktop()) return;
- if (this.opts.toolbarExternal) return;
- if (!this.opts.toolbarFixed) return;
- this.toolbar.observeScroll();
- $(this.opts.toolbarFixedTarget).on('scroll.redactor.' + this.uuid, $.proxy(this.toolbar.observeScroll, this));
- },
- setOverflow: function()
- {
- if (this.utils.isMobile() && this.opts.toolbarOverflow)
- {
- this.$toolbar.addClass('redactor-toolbar-overflow');
- }
- },
- isButtonSourceNeeded: function()
- {
- if (this.opts.source) return;
- var index = this.opts.buttons.indexOf('html');
- if (index !== -1)
- {
- this.opts.buttons.splice(index, 1);
- }
- },
- hideButtons: function()
- {
- if (this.opts.buttonsHide.length === 0) return;
- $.each(this.opts.buttonsHide, $.proxy(function(i, s)
- {
- var index = this.opts.buttons.indexOf(s);
- this.opts.buttons.splice(index, 1);
- }, this));
- },
- hideButtonsOnMobile: function()
- {
- if (!this.utils.isMobile() || this.opts.buttonsHideOnMobile.length === 0) return;
- $.each(this.opts.buttonsHideOnMobile, $.proxy(function(i, s)
- {
- var index = this.opts.buttons.indexOf(s);
- this.opts.buttons.splice(index, 1);
- }, this));
- },
- observeScroll: function()
- {
- var scrollTop = $(this.opts.toolbarFixedTarget).scrollTop();
- var boxTop = 1;
- if (this.opts.toolbarFixedTarget === document)
- {
- boxTop = this.$box.offset().top;
- }
- if ((scrollTop + this.opts.toolbarFixedTopOffset) > boxTop)
- {
- this.toolbar.observeScrollEnable(scrollTop, boxTop);
- }
- else
- {
- this.toolbar.observeScrollDisable();
- }
- },
- observeScrollEnable: function(scrollTop, boxTop)
- {
- var top = this.opts.toolbarFixedTopOffset + scrollTop - boxTop;
- var left = 0;
- var end = boxTop + this.$box.height() - 32;
- var width = this.$box.innerWidth();
- this.$toolbar.addClass('toolbar-fixed-box');
- this.$toolbar.css({
- position: 'absolute',
- width: width,
- top: top + 'px',
- left: left
- });
- if (scrollTop > end)
- $('.redactor-dropdown-' + this.uuid + ':visible').hide();
- this.toolbar.setDropdownsFixed();
- this.$toolbar.css('visibility', (scrollTop < end) ? 'visible' : 'hidden');
- },
- observeScrollDisable: function()
- {
- this.$toolbar.css({
- position: 'relative',
- width: 'auto',
- top: 0,
- left: 0,
- visibility: 'visible'
- });
- this.toolbar.unsetDropdownsFixed();
- this.$toolbar.removeClass('toolbar-fixed-box');
- },
- setDropdownsFixed: function()
- {
- var top = this.$toolbar.innerHeight() + this.opts.toolbarFixedTopOffset;
- var position = 'fixed';
- if (this.opts.toolbarFixedTarget !== document)
- {
- top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top) + this.opts.toolbarFixedTopOffset;
- position = 'absolute';
- }
- $('.redactor-dropdown-' + this.uuid).each(function()
- {
- $(this).css({ position: position, top: top + 'px' });
- });
- },
- unsetDropdownsFixed: function()
- {
- var top = (this.$toolbar.innerHeight() + this.$toolbar.offset().top);
- $('.redactor-dropdown-' + this.uuid).each(function()
- {
- $(this).css({ position: 'absolute', top: top + 'px' });
- });
- }
- };
- },
- upload: function()
- {
- return {
- init: function(id, url, callback)
- {
- this.upload.direct = false;
- this.upload.callback = callback;
- this.upload.url = url;
- this.upload.$el = $(id);
- this.upload.$droparea = $('<div id="redactor-droparea" />');
- this.upload.$placeholdler = $('<div id="redactor-droparea-placeholder" />').text(this.lang.get('upload_label'));
- this.upload.$input = $('<input type="file" name="file" />');
- this.upload.$placeholdler.append(this.upload.$input);
- this.upload.$droparea.append(this.upload.$placeholdler);
- this.upload.$el.append(this.upload.$droparea);
- this.upload.$droparea.off('redactor.upload');
- this.upload.$input.off('redactor.upload');
- this.upload.$droparea.on('dragover.redactor.upload', $.proxy(this.upload.onDrag, this));
- this.upload.$droparea.on('dragleave.redactor.upload', $.proxy(this.upload.onDragLeave, this));
- // change
- this.upload.$input.on('change.redactor.upload', $.proxy(function(e)
- {
- e = e.originalEvent || e;
- this.upload.traverseFile(this.upload.$input[0].files[0], e);
- }, this));
- // drop
- this.upload.$droparea.on('drop.redactor.upload', $.proxy(function(e)
- {
- e.preventDefault();
- this.upload.$droparea.removeClass('drag-hover').addClass('drag-drop');
- this.upload.onDrop(e);
- }, this));
- },
- directUpload: function(file, e)
- {
- this.upload.direct = true;
- this.upload.traverseFile(file, e);
- },
- onDrop: function(e)
- {
- e = e.originalEvent || e;
- var files = e.dataTransfer.files;
- this.upload.traverseFile(files[0], e);
- },
- traverseFile: function(file, e)
- {
- if (this.opts.s3)
- {
- this.upload.setConfig(file);
- this.upload.s3uploadFile(file);
- return;
- }
- var formData = !!window.FormData ? new FormData() : null;
- if (window.FormData)
- {
- this.upload.setConfig(file);
- var name = (this.upload.type == 'image') ? this.opts.imageUploadParam : this.opts.fileUploadParam;
- formData.append(name, file);
- }
- this.progress.show();
- this.core.setCallback('uploadStart', e, formData);
- this.upload.sendData(formData, e);
- },
- setConfig: function(file)
- {
- this.upload.getType(file);
- if (this.upload.direct)
- {
- this.upload.url = (this.upload.type == 'image') ? this.opts.imageUpload : this.opts.fileUpload;
- this.upload.callback = (this.upload.type == 'image') ? this.image.insert : this.file.insert;
- }
- },
- getType: function(file)
- {
- this.upload.type = 'image';
- if (this.opts.imageTypes.indexOf(file.type) == -1)
- {
- this.upload.type = 'file';
- }
- },
- getHiddenFields: function(obj, fd)
- {
- if (obj === false || typeof obj !== 'object') return fd;
- $.each(obj, $.proxy(function(k, v)
- {
- if (v !== null && v.toString().indexOf('#') === 0) v = $(v).val();
- fd.append(k, v);
- }, this));
- return fd;
- },
- sendData: function(formData, e)
- {
- // append hidden fields
- if (this.upload.type == 'image')
- {
- formData = this.upload.getHiddenFields(this.opts.uploadImageFields, formData);
- formData = this.upload.getHiddenFields(this.upload.imageFields, formData);
- }
- else
- {
- formData = this.upload.getHiddenFields(this.opts.uploadFileFields, formData);
- formData = this.upload.getHiddenFields(this.upload.fileFields, formData);
- }
- var xhr = new XMLHttpRequest();
- xhr.open('POST', this.upload.url);
- xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
- // complete
- xhr.onreadystatechange = $.proxy(function()
- {
- if (xhr.readyState == 4)
- {
- var data = xhr.responseText;
- data = data.replace(/^\[/, '');
- data = data.replace(/\]$/, '');
- var json;
- try
- {
- json = (typeof data === 'string' ? $.parseJSON(data) : data);
- }
- catch(err)
- {
- json = {
- error: true
- };
- }
- this.progress.hide();
- if (!this.upload.direct)
- {
- this.upload.$droparea.removeClass('drag-drop');
- }
- this.upload.callback(json, this.upload.direct, e);
- }
- }, this);
- /*
- xhr.upload.onprogress = $.proxy(function(e)
- {
- if (e.lengthComputable)
- {
- var complete = (e.loaded / e.total * 100 | 0);
- //progress.value = progress.innerHTML = complete;
- }
- }, this);
- */
- xhr.send(formData);
- },
- onDrag: function(e)
- {
- e.preventDefault();
- this.upload.$droparea.addClass('drag-hover');
- },
- onDragLeave: function(e)
- {
- e.preventDefault();
- this.upload.$droparea.removeClass('drag-hover');
- },
- clearImageFields: function()
- {
- this.upload.imageFields = {};
- },
- addImageFields: function(name, value)
- {
- this.upload.imageFields[name] = value;
- },
- removeImageFields: function(name)
- {
- delete this.upload.imageFields[name];
- },
- clearFileFields: function()
- {
- this.upload.fileFields = {};
- },
- addFileFields: function(name, value)
- {
- this.upload.fileFields[name] = value;
- },
- removeFileFields: function(name)
- {
- delete this.upload.fileFields[name];
- },
- // S3
- s3uploadFile: function(file)
- {
- this.upload.s3executeOnSignedUrl(file, $.proxy(function(signedURL)
- {
- this.upload.s3uploadToS3(file, signedURL);
- }, this));
- },
- s3executeOnSignedUrl: function(file, callback)
- {
- var xhr = new XMLHttpRequest();
- var mark = (this.opts.s3.search(/\?/) !== '-1') ? '?' : '&';
- xhr.open('GET', this.opts.s3 + mark + 'name=' + file.name + '&type=' + file.type, true);
- // Hack to pass bytes through unprocessed.
- if (xhr.overrideMimeType) xhr.overrideMimeType('text/plain; charset=x-user-defined');
- var that = this;
- xhr.onreadystatechange = function(e)
- {
- if (this.readyState == 4 && this.status == 200)
- {
- that.progress.show();
- callback(decodeURIComponent(this.responseText));
- }
- else if (this.readyState == 4 && this.status != 200)
- {
- //setProgress(0, 'Could not contact signing script. Status = ' + this.status);
- }
- };
- xhr.send();
- },
- s3createCORSRequest: function(method, url)
- {
- var xhr = new XMLHttpRequest();
- if ("withCredentials" in xhr)
- {
- xhr.open(method, url, true);
- }
- else if (typeof XDomainRequest != "undefined")
- {
- xhr = new XDomainRequest();
- xhr.open(method, url);
- }
- else
- {
- xhr = null;
- }
- return xhr;
- },
- s3uploadToS3: function(file, url)
- {
- var xhr = this.upload.s3createCORSRequest('PUT', url);
- if (!xhr)
- {
- //setProgress(0, 'CORS not supported');
- }
- else
- {
- xhr.onload = $.proxy(function()
- {
- if (xhr.status == 200)
- {
- //setProgress(100, 'Upload completed.');
- this.progress.hide();
- var s3file = url.split('?');
- if (!s3file[0])
- {
- // url parsing is fail
- return false;
- }
- if (!this.upload.direct)
- {
- this.upload.$droparea.removeClass('drag-drop');
- }
- var json = { filelink: s3file[0] };
- if (this.upload.type == 'file')
- {
- var arr = s3file[0].split('/');
- json.filename = arr[arr.length-1];
- }
- this.upload.callback(json, this.upload.direct, false);
- }
- else
- {
- //setProgress(0, 'Upload error: ' + xhr.status);
- }
- }, this);
- xhr.onerror = function() {};
- xhr.upload.onprogress = function(e) {};
- xhr.setRequestHeader('Content-Type', file.type);
- xhr.setRequestHeader('x-amz-acl', 'public-read');
- xhr.send(file);
- }
- }
- };
- },
- utils: function()
- {
- return {
- isMobile: function()
- {
- return /(iPhone|iPod|BlackBerry|Android)/.test(navigator.userAgent);
- },
- isDesktop: function()
- {
- return !/(iPhone|iPod|iPad|BlackBerry|Android)/.test(navigator.userAgent);
- },
- isString: function(obj)
- {
- return Object.prototype.toString.call(obj) == '[object String]';
- },
- isEmpty: function(html, removeEmptyTags)
- {
- html = html.replace(/[\u200B-\u200D\uFEFF]/g, '');
- html = html.replace(/ /gi, '');
- html = html.replace(/<\/?br\s?\/?>/g, '');
- html = html.replace(/\s/g, '');
- html = html.replace(/^<p>[^\W\w\D\d]*?<\/p>$/i, '');
- html = html.replace(/<iframe(.*?[^>])>$/i, 'iframe');
- html = html.replace(/<source(.*?[^>])>$/i, 'source');
- // remove empty tags
- if (removeEmptyTags !== false)
- {
- html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
- html = html.replace(/<[^\/>][^>]*><\/[^>]+>/gi, '');
- }
- html = $.trim(html);
- return html === '';
- },
- normalize: function(str)
- {
- if (typeof(str) === 'undefined') return 0;
- return parseInt(str.replace('px',''), 10);
- },
- hexToRgb: function(hex)
- {
- if (typeof hex == 'undefined') return;
- if (hex.search(/^#/) == -1) return hex;
- var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
- hex = hex.replace(shorthandRegex, function(m, r, g, b)
- {
- return r + r + g + g + b + b;
- });
- var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
- return 'rgb(' + parseInt(result[1], 16) + ', ' + parseInt(result[2], 16) + ', ' + parseInt(result[3], 16) + ')';
- },
- getOuterHtml: function(el)
- {
- return $('<div>').append($(el).eq(0).clone()).html();
- },
- getAlignmentElement: function(el)
- {
- if ($.inArray(el.tagName, this.opts.alignmentTags) !== -1)
- {
- return $(el);
- }
- else
- {
- return $(el).closest(this.opts.alignmentTags.toString().toLowerCase(), this.$editor[0]);
- }
- },
- removeEmptyAttr: function(el, attr)
- {
- var $el = $(el);
- if (typeof $el.attr(attr) == 'undefined')
- {
- return true;
- }
- if ($el.attr(attr) === '')
- {
- $el.removeAttr(attr);
- return true;
- }
- return false;
- },
- removeEmpty: function(i, s)
- {
- var $s = $($.parseHTML(s));
- $s.find('.redactor-invisible-space').removeAttr('style').removeAttr('class');
- if ($s.find('hr, br, img, iframe, source').length !== 0) return;
- var text = $.trim($s.text());
- if (this.utils.isEmpty(text, false))
- {
- $s.remove();
- }
- },
- // save and restore scroll
- saveScroll: function()
- {
- this.saveEditorScroll = this.$editor.scrollTop();
- this.saveBodyScroll = $(window).scrollTop();
- if (this.opts.scrollTarget) this.saveTargetScroll = $(this.opts.scrollTarget).scrollTop();
- },
- restoreScroll: function()
- {
- if (typeof this.saveScroll === 'undefined' && typeof this.saveBodyScroll === 'undefined') return;
- $(window).scrollTop(this.saveBodyScroll);
- this.$editor.scrollTop(this.saveEditorScroll);
- if (this.opts.scrollTarget) $(this.opts.scrollTarget).scrollTop(this.saveTargetScroll);
- },
- // get invisible space element
- createSpaceElement: function()
- {
- var space = document.createElement('span');
- space.className = 'redactor-invisible-space';
- space.innerHTML = this.opts.invisibleSpace;
- return space;
- },
- // replace
- removeInlineTags: function(node)
- {
- var tags = this.opts.inlineTags;
- tags.push('span');
- if (node.tagName == 'PRE') tags.push('a');
- $(node).find(tags.join(',')).not('span.redactor-selection-marker').contents().unwrap();
- },
- replaceWithContents: function(node, removeInlineTags)
- {
- var self = this;
- $(node).replaceWith(function()
- {
- if (removeInlineTags === true) self.utils.removeInlineTags(this);
- return $(this).contents();
- });
- return $(node);
- },
- replaceToTag: function(node, tag, removeInlineTags)
- {
- var replacement;
- var self = this;
- $(node).replaceWith(function()
- {
- replacement = $('<' + tag + ' />').append($(this).contents());
- for (var i = 0; i < this.attributes.length; i++)
- {
- replacement.attr(this.attributes[i].name, this.attributes[i].value);
- }
- if (removeInlineTags === true) self.utils.removeInlineTags(replacement);
- return replacement;
- });
- return replacement;
- },
- // start and end of element
- isStartOfElement: function()
- {
- var block = this.selection.getBlock();
- if (!block) return false;
- var offset = this.caret.getOffsetOfElement(block);
- return (offset === 0) ? true : false;
- },
- isEndOfElement: function(element)
- {
- if (typeof element == 'undefined')
- {
- var element = this.selection.getBlock();
- if (!element) return false;
- }
- var offset = this.caret.getOffsetOfElement(element);
- var text = $.trim($(element).text()).replace(/\n\r\n/g, '');
- return (offset == text.length) ? true : false;
- },
- isStartOfEditor: function()
- {
- var offset = this.caret.getOffsetOfElement(this.$editor[0]);
- return (offset === 0) ? true : false;
- },
- isEndOfEditor: function()
- {
- var block = this.$editor[0];
- var offset = this.caret.getOffsetOfElement(block);
- var text = $.trim($(block).html().replace(/(<([^>]+)>)/gi,''));
- return (offset == text.length) ? true : false;
- },
- // test blocks
- isBlock: function(block)
- {
- block = block[0] || block;
- return block && this.utils.isBlockTag(block.tagName);
- },
- isBlockTag: function(tag)
- {
- if (typeof tag == 'undefined') return false;
- return this.reIsBlock.test(tag);
- },
- // tag detection
- isTag: function(current, tag)
- {
- var element = $(current).closest(tag, this.$editor[0]);
- if (element.length == 1)
- {
- return element[0];
- }
- return false;
- },
- // select all
- isSelectAll: function()
- {
- return this.selectAll;
- },
- enableSelectAll: function()
- {
- this.selectAll = true;
- },
- disableSelectAll: function()
- {
- this.selectAll = false;
- },
- // parents detection
- isRedactorParent: function(el)
- {
- if (!el)
- {
- return false;
- }
- if ($(el).parents('.redactor-editor').length === 0 || $(el).hasClass('redactor-editor'))
- {
- return false;
- }
- return el;
- },
- isCurrentOrParentHeader: function()
- {
- return this.utils.isCurrentOrParent(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
- },
- isCurrentOrParent: function(tagName)
- {
- var parent = this.selection.getParent();
- var current = this.selection.getCurrent();
- if ($.isArray(tagName))
- {
- var matched = 0;
- $.each(tagName, $.proxy(function(i, s)
- {
- if (this.utils.isCurrentOrParentOne(current, parent, s))
- {
- matched++;
- }
- }, this));
- return (matched === 0) ? false : true;
- }
- else
- {
- return this.utils.isCurrentOrParentOne(current, parent, tagName);
- }
- },
- isCurrentOrParentOne: function(current, parent, tagName)
- {
- tagName = tagName.toUpperCase();
- return parent && parent.tagName === tagName ? parent : current && current.tagName === tagName ? current : false;
- },
- // browsers detection
- isOldIe: function()
- {
- return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 9) ? true : false;
- },
- isLessIe10: function()
- {
- return (this.utils.browser('msie') && parseInt(this.utils.browser('version'), 10) < 10) ? true : false;
- },
- isIe11: function()
- {
- return !!navigator.userAgent.match(/Trident\/7\./);
- },
- browser: function(browser)
- {
- var ua = navigator.userAgent.toLowerCase();
- var match = /(opr)[\/]([\w.]+)/.exec( ua ) ||
- /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
- /(webkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) ||
- /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
- /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
- /(msie) ([\w.]+)/.exec( ua ) ||
- ua.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec( ua ) ||
- ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
- [];
- if (browser == 'safari') return (typeof match[3] != 'undefined') ? match[3] == 'safari' : false;
- if (browser == 'version') return match[2];
- if (browser == 'webkit') return (match[1] == 'chrome' || match[1] == 'opr' || match[1] == 'webkit');
- if (match[1] == 'rv') return browser == 'msie';
- if (match[1] == 'opr') return browser == 'webkit';
- return browser == match[1];
- },
- strpos: function(haystack, needle, offset)
- {
- var i = haystack.indexOf(needle, offset);
- return i >= 0 ? i : false;
- },
- disableBodyScroll: function()
- {
- var $body = $('html');
- var windowWidth = window.innerWidth;
- if (!windowWidth)
- {
- var documentElementRect = document.documentElement.getBoundingClientRect();
- windowWidth = documentElementRect.right - Math.abs(documentElementRect.left);
- }
- var isOverflowing = document.body.clientWidth < windowWidth;
- var scrollbarWidth = this.utils.measureScrollbar();
- $body.css('overflow', 'hidden');
- if (isOverflowing) $body.css('padding-right', scrollbarWidth);
- },
- measureScrollbar: function()
- {
- var $body = $('body');
- var scrollDiv = document.createElement('div');
- scrollDiv.className = 'redactor-scrollbar-measure';
- $body.append(scrollDiv);
- var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
- $body[0].removeChild(scrollDiv);
- return scrollbarWidth;
- },
- enableBodyScroll: function()
- {
- $('html').css({ 'overflow': '', 'padding-right': '' });
- $('body').remove('redactor-scrollbar-measure');
- }
- };
- }
- };
- $(window).on('load.tools.redactor', function()
- {
- $('[data-tools="redactor"]').redactor();
- });
- // constructor
- Redactor.prototype.init.prototype = Redactor.prototype;
- })(jQuery);
|