12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632 |
- require({cache:{
- 'dijit/_editor/plugins/BuxEnterKeyHandling':function(){
- define("dijit/_editor/plugins/BuxEnterKeyHandling", [
- "dojo/_base/declare", // declare
- "dojo/dom-construct", // domConstruct.destroy domConstruct.place
- "dojo/_base/event", // event.stop
- "dojo/keys", // keys.ENTER
- "dojo/_base/lang",
- "dojo/_base/sniff", // has("ie") has("mozilla") has("webkit")
- "dojo/_base/window", // win.global win.withGlobal
- "dojo/window", // winUtils.scrollIntoView
- "../_Plugin",
- "../BuxRichText",
- "../range",
- "../selection"
- ], function(declare, domConstruct, event, keys, lang, has, win, winUtils, _Plugin, BuxRichText, rangeapi, selectionapi){
- /*=====
- var _Plugin = dijit._editor._Plugin;
- =====*/
- // module:
- // dijit/_editor/plugins/EnterKeyHandling
- // summary:
- // This plugin tries to make all browsers behave consistently with regard to
- // how ENTER behaves in the editor window. It traps the ENTER key and alters
- // the way DOM is constructed in certain cases to try to commonize the generated
- // DOM and behaviors across browsers.
- return declare("dijit._editor.plugins.EnterKeyHandling", _Plugin, {
- // summary:
- // This plugin tries to make all browsers behave consistently with regard to
- // how ENTER behaves in the editor window. It traps the ENTER key and alters
- // the way DOM is constructed in certain cases to try to commonize the generated
- // DOM and behaviors across browsers.
- //
- // description:
- // This plugin has three modes:
- //
- // * blockNodeForEnter=BR
- // * blockNodeForEnter=DIV
- // * blockNodeForEnter=P
- //
- // In blockNodeForEnter=P, the ENTER key starts a new
- // paragraph, and shift-ENTER starts a new line in the current paragraph.
- // For example, the input:
- //
- // | first paragraph <shift-ENTER>
- // | second line of first paragraph <ENTER>
- // | second paragraph
- //
- // will generate:
- //
- // | <p>
- // | first paragraph
- // | <br/>
- // | second line of first paragraph
- // | </p>
- // | <p>
- // | second paragraph
- // | </p>
- //
- // In BR and DIV mode, the ENTER key conceptually goes to a new line in the
- // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice.
- // For example, if the user enters text into an editor like this:
- //
- // | one <ENTER>
- // | two <ENTER>
- // | three <ENTER>
- // | <ENTER>
- // | four <ENTER>
- // | five <ENTER>
- // | six <ENTER>
- //
- // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates:
- //
- // BR:
- // | one<br/>
- // | two<br/>
- // | three<br/>
- // | <br/>
- // | four<br/>
- // | five<br/>
- // | six<br/>
- //
- // DIV:
- // | <div>one</div>
- // | <div>two</div>
- // | <div>three</div>
- // | <div> </div>
- // | <div>four</div>
- // | <div>five</div>
- // | <div>six</div>
- // blockNodeForEnter: String
- // This property decides the behavior of Enter key. It can be either P,
- // DIV, BR, or empty (which means disable this feature). Anything else
- // will trigger errors. The default is 'BR'
- //
- // See class description for more details.
- blockNodeForEnter: 'BR',
- constructor: function(args){
- if(args){
- if("blockNodeForEnter" in args){
- args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase();
- }
- lang.mixin(this,args);
- }
- },
- setEditor: function(editor){
- // Overrides _Plugin.setEditor().
- if(this.editor === editor){ return; }
- this.editor = editor;
- if(this.blockNodeForEnter == 'BR'){
- // While Moz has a mode tht mostly works, it's still a little different,
- // So, try to just have a common mode and be consistent. Which means
- // we need to enable customUndo, if not already enabled.
- this.editor.customUndo = true;
- editor.onLoadDeferred.then(lang.hitch(this,function(d){
- this.connect(editor.document, "onkeypress", function(e){
- if(e.charOrCode == keys.ENTER){
- // Just do it manually. The handleEnterKey has a shift mode that
- // Always acts like <br>, so just use it.
- var ne = lang.mixin({},e);
- ne.shiftKey = true;
- if(!this.handleEnterKey(ne)){
- event.stop(e);
- }
- }
- });
- if(has("ie") >= 9){
- this.connect(editor.document, "onpaste", function(e){
- setTimeout(dojo.hitch(this, function(){
- // Use the old range/selection code to kick IE 9 into updating
- // its range by moving it back, then forward, one 'character'.
- var r = this.editor.document.selection.createRange();
- r.move('character',-1);
- r.select();
- r.move('character',1);
- r.select();
- }),0);
- });
- }
- return d;
- }));
- }else if(this.blockNodeForEnter){
- // add enter key handler
- // FIXME: need to port to the new event code!!
- var h = lang.hitch(this,this.handleEnterKey);
- editor.addKeyHandler(13, 0, 0, h); //enter
- editor.addKeyHandler(13, 0, 1, h); //shift+enter
- this.connect(this.editor,'onKeyPressed','onKeyPressed');
- }
- },
- onKeyPressed: function(){
- // summary:
- // Handler for keypress events.
- // tags:
- // private
- if(this._checkListLater){
- if(win.withGlobal(this.editor.window, 'isCollapsed', dijit)){
- var liparent=win.withGlobal(this.editor.window, 'getAncestorElement', selectionapi, ['LI']);
- if(!liparent){
- // circulate the undo detection code by calling BuxRichText::execCommand directly
- BuxRichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
- // set the innerHTML of the new block node
- var block = win.withGlobal(this.editor.window, 'getAncestorElement', selectionapi, [this.blockNodeForEnter]);
- if(block){
- block.innerHTML=this.bogusHtmlContent;
- if(has("ie") <= 9){
- // move to the start by moving backwards one char
- var r = this.editor.document.selection.createRange();
- r.move('character',-1);
- r.select();
- }
- }else{
- console.error('onKeyPressed: Cannot find the new block node'); // FIXME
- }
- }else{
- if(has("mozilla")){
- if(liparent.parentNode.parentNode.nodeName == 'LI'){
- liparent=liparent.parentNode.parentNode;
- }
- }
- var fc=liparent.firstChild;
- if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){
- liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc);
- var newrange = rangeapi.create(this.editor.window);
- newrange.setStart(liparent.firstChild,0);
- var selection = rangeapi.getSelection(this.editor.window, true);
- selection.removeAllRanges();
- selection.addRange(newrange);
- }
- }
- }
- this._checkListLater = false;
- }
- if(this._pressedEnterInBlock){
- // the new created is the original current P, so we have previousSibling below
- if(this._pressedEnterInBlock.previousSibling){
- this.removeTrailingBr(this._pressedEnterInBlock.previousSibling);
- }
- delete this._pressedEnterInBlock;
- }
- },
- // bogusHtmlContent: [private] String
- // HTML to stick into a new empty block
- bogusHtmlContent: ' ', //
- // blockNodes: [private] Regex
- // Regex for testing if a given tag is a block level (display:block) tag
- blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/,
- handleEnterKey: function(e){
- // summary:
- // Handler for enter key events when blockNodeForEnter is DIV or P.
- // description:
- // Manually handle enter key event to make the behavior consistent across
- // all supported browsers. See class description for details.
- // tags:
- // private
- var selection, range, newrange, startNode, endNode, brNode, doc=this.editor.document,br,rs,txt;
- if(e.shiftKey){ // shift+enter always generates <br>
- var parent = win.withGlobal(this.editor.window, "getParentElement", selectionapi);
- var header = rangeapi.getAncestor(parent,this.blockNodes);
- if(header){
- if(header.tagName == 'LI'){
- return true; // let browser handle
- }
- selection = rangeapi.getSelection(this.editor.window);
- range = selection.getRangeAt(0);
- if(!range.collapsed){
- range.deleteContents();
- selection = rangeapi.getSelection(this.editor.window);
- range = selection.getRangeAt(0);
- }
- if(rangeapi.atBeginningOfContainer(header, range.startContainer, range.startOffset)){
- br=doc.createElement('br');
- newrange = rangeapi.create(this.editor.window);
- header.insertBefore(br,header.firstChild);
- newrange.setStartAfter(br);
- selection.removeAllRanges();
- selection.addRange(newrange);
- }else if(rangeapi.atEndOfContainer(header, range.startContainer, range.startOffset)){
- newrange = rangeapi.create(this.editor.window);
- br=doc.createElement('br');
- header.appendChild(br);
- header.appendChild(doc.createTextNode('\xA0'));
- newrange.setStart(header.lastChild,0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- }else{
- rs = range.startContainer;
- if(rs && rs.nodeType == 3){
- // Text node, we have to split it.
- txt = rs.nodeValue;
- win.withGlobal(this.editor.window, function(){
- startNode = doc.createTextNode(txt.substring(0, range.startOffset));
- endNode = doc.createTextNode(txt.substring(range.startOffset));
- brNode = doc.createElement("br");
- if(endNode.nodeValue == "" && has("webkit")){
- endNode = doc.createTextNode('\xA0')
- }
- domConstruct.place(startNode, rs, "after");
- domConstruct.place(brNode, startNode, "after");
- domConstruct.place(endNode, brNode, "after");
- domConstruct.destroy(rs);
- newrange = rangeapi.create();
- newrange.setStart(endNode,0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- });
- return false;
- }
- return true; // let browser handle
- }
- }else{
- selection = rangeapi.getSelection(this.editor.window);
- if(selection.rangeCount){
- range = selection.getRangeAt(0);
- if(range && range.startContainer){
- if(!range.collapsed){
- range.deleteContents();
- selection = rangeapi.getSelection(this.editor.window);
- range = selection.getRangeAt(0);
- }
- rs = range.startContainer;
- if(rs && rs.nodeType == 3){
- // Text node, we have to split it.
- win.withGlobal(this.editor.window, lang.hitch(this, function(){
- var endEmpty = false;
- var offset = range.startOffset;
- if(rs.length < offset){
- //We are not splitting the right node, try to locate the correct one
- ret = this._adjustNodeAndOffset(rs, offset);
- rs = ret.node;
- offset = ret.offset;
- }
- txt = rs.nodeValue;
- startNode = doc.createTextNode(txt.substring(0, offset));
- endNode = doc.createTextNode(txt.substring(offset));
- brNode = doc.createElement("br");
- if(!endNode.length){
- endNode = doc.createTextNode('\xA0');
- endEmpty = true;
- }
- if(startNode.length){
- domConstruct.place(startNode, rs, "after");
- }else{
- startNode = rs;
- }
- domConstruct.place(brNode, startNode, "after");
- domConstruct.place(endNode, brNode, "after");
- domConstruct.destroy(rs);
- newrange = rangeapi.create();
- newrange.setStart(endNode,0);
- newrange.setEnd(endNode, endNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- if(endEmpty && !has("webkit")){
- selectionapi.remove();
- }else{
- selectionapi.collapse(true);
- }
- }));
- }else{
- var targetNode;
- if(range.startOffset >= 0){
- targetNode = rs.childNodes[range.startOffset];
- }
- win.withGlobal(this.editor.window, lang.hitch(this, function(){
- var brNode = doc.createElement("br");
- var endNode = doc.createTextNode('\xA0');
- if(!targetNode){
- rs.appendChild(brNode);
- rs.appendChild(endNode);
- }else{
- domConstruct.place(brNode, targetNode, "before");
- domConstruct.place(endNode, brNode, "after");
- }
- newrange = rangeapi.create(win.global);
- newrange.setStart(endNode,0);
- newrange.setEnd(endNode, endNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- selectionapi.collapse(true);
- }));
- }
- }
- }else{
- // don't change this: do not call this.execCommand, as that may have other logic in subclass
- BuxRichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>');
- }
- }
- return false;
- }
- var _letBrowserHandle = true;
- // first remove selection
- selection = rangeapi.getSelection(this.editor.window);
- range = selection.getRangeAt(0);
- if(!range.collapsed){
- range.deleteContents();
- selection = rangeapi.getSelection(this.editor.window);
- range = selection.getRangeAt(0);
- }
- var block = rangeapi.getBlockAncestor(range.endContainer, null, this.editor.editNode);
- var blockNode = block.blockNode;
- // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it
- if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){
- if(has("mozilla")){
- // press enter in middle of P may leave a trailing <br/>, let's remove it later
- this._pressedEnterInBlock = blockNode;
- }
- // if this li only contains spaces, set the content to empty so the browser will outdent this item
- if(/^(\s| | |\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s| | |\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){
- // empty LI node
- blockNode.innerHTML = '';
- if(has("webkit")){ // WebKit tosses the range when innerHTML is reset
- newrange = rangeapi.create(this.editor.window);
- newrange.setStart(blockNode, 0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- }
- this._checkListLater = false; // nothing to check since the browser handles outdent
- }
- return true;
- }
- // text node directly under body, let's wrap them in a node
- if(!block.blockNode || block.blockNode===this.editor.editNode){
- try{
- BuxRichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
- }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ }
- // get the newly created block node
- // FIXME
- block = {blockNode:win.withGlobal(this.editor.window, "getAncestorElement", selectionapi, [this.blockNodeForEnter]),
- blockContainer: this.editor.editNode};
- if(block.blockNode){
- if(block.blockNode != this.editor.editNode &&
- (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){
- this.removeTrailingBr(block.blockNode);
- return false;
- }
- }else{ // we shouldn't be here if formatblock worked
- block.blockNode = this.editor.editNode;
- }
- selection = rangeapi.getSelection(this.editor.window);
- range = selection.getRangeAt(0);
- }
- var newblock = doc.createElement(this.blockNodeForEnter);
- newblock.innerHTML=this.bogusHtmlContent;
- this.removeTrailingBr(block.blockNode);
- var endOffset = range.endOffset;
- var node = range.endContainer;
- if(node.length < endOffset){
- //We are not checking the right node, try to locate the correct one
- var ret = this._adjustNodeAndOffset(node, endOffset);
- node = ret.node;
- endOffset = ret.offset;
- }
- if(rangeapi.atEndOfContainer(block.blockNode, node, endOffset)){
- if(block.blockNode === block.blockContainer){
- block.blockNode.appendChild(newblock);
- }else{
- domConstruct.place(newblock, block.blockNode, "after");
- }
- _letBrowserHandle = false;
- // lets move caret to the newly created block
- newrange = rangeapi.create(this.editor.window);
- newrange.setStart(newblock, 0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- if(this.editor.height){
- winUtils.scrollIntoView(newblock);
- }
- }else if(rangeapi.atBeginningOfContainer(block.blockNode,
- range.startContainer, range.startOffset)){
- domConstruct.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before");
- if(newblock.nextSibling && this.editor.height){
- // position input caret - mostly WebKit needs this
- newrange = rangeapi.create(this.editor.window);
- newrange.setStart(newblock.nextSibling, 0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- // browser does not scroll the caret position into view, do it manually
- winUtils.scrollIntoView(newblock.nextSibling);
- }
- _letBrowserHandle = false;
- }else{ //press enter in the middle of P/DIV/Whatever/
- if(block.blockNode === block.blockContainer){
- block.blockNode.appendChild(newblock);
- }else{
- domConstruct.place(newblock, block.blockNode, "after");
- }
- _letBrowserHandle = false;
- // Clone any block level styles.
- if(block.blockNode.style){
- if(newblock.style){
- if(block.blockNode.style.cssText){
- newblock.style.cssText = block.blockNode.style.cssText;
- }
- }
- }
- // Okay, we probably have to split.
- rs = range.startContainer;
- var firstNodeMoved;
- if(rs && rs.nodeType == 3){
- // Text node, we have to split it.
- var nodeToMove, tNode;
- endOffset = range.endOffset;
- if(rs.length < endOffset){
- //We are not splitting the right node, try to locate the correct one
- ret = this._adjustNodeAndOffset(rs, endOffset);
- rs = ret.node;
- endOffset = ret.offset;
- }
- txt = rs.nodeValue;
- startNode = doc.createTextNode(txt.substring(0, endOffset));
- endNode = doc.createTextNode(txt.substring(endOffset, txt.length));
- // Place the split, then remove original nodes.
- domConstruct.place(startNode, rs, "before");
- domConstruct.place(endNode, rs, "after");
- domConstruct.destroy(rs);
- // Okay, we split the text. Now we need to see if we're
- // parented to the block element we're splitting and if
- // not, we have to split all the way up. Ugh.
- var parentC = startNode.parentNode;
- while(parentC !== block.blockNode){
- var tg = parentC.tagName;
- var newTg = doc.createElement(tg);
- // Clone over any 'style' data.
- if(parentC.style){
- if(newTg.style){
- if(parentC.style.cssText){
- newTg.style.cssText = parentC.style.cssText;
- }
- }
- }
- // If font also need to clone over any font data.
- if(parentC.tagName === "FONT"){
- if(parentC.color){
- newTg.color = parentC.color;
- }
- if(parentC.face){
- newTg.face = parentC.face;
- }
- if(parentC.size){ // this check was necessary on IE
- newTg.size = parentC.size;
- }
- }
- nodeToMove = endNode;
- while(nodeToMove){
- tNode = nodeToMove.nextSibling;
- newTg.appendChild(nodeToMove);
- nodeToMove = tNode;
- }
- domConstruct.place(newTg, parentC, "after");
- startNode = parentC;
- endNode = newTg;
- parentC = parentC.parentNode;
- }
- // Lastly, move the split out tags to the new block.
- // as they should now be split properly.
- nodeToMove = endNode;
- if(nodeToMove.nodeType == 1 || (nodeToMove.nodeType == 3 && nodeToMove.nodeValue)){
- // Non-blank text and non-text nodes need to clear out that blank space
- // before moving the contents.
- newblock.innerHTML = "";
- }
- firstNodeMoved = nodeToMove;
- while(nodeToMove){
- tNode = nodeToMove.nextSibling;
- newblock.appendChild(nodeToMove);
- nodeToMove = tNode;
- }
- }
- //lets move caret to the newly created block
- newrange = rangeapi.create(this.editor.window);
- var nodeForCursor;
- var innerMostFirstNodeMoved = firstNodeMoved;
- if(this.blockNodeForEnter !== 'BR'){
- while(innerMostFirstNodeMoved){
- nodeForCursor = innerMostFirstNodeMoved;
- tNode = innerMostFirstNodeMoved.firstChild;
- innerMostFirstNodeMoved = tNode;
- }
- if(nodeForCursor && nodeForCursor.parentNode){
- newblock = nodeForCursor.parentNode;
- newrange.setStart(newblock, 0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- if(this.editor.height){
- winUtils.scrollIntoView(newblock);
- }
- if(has("mozilla")){
- // press enter in middle of P may leave a trailing <br/>, let's remove it later
- this._pressedEnterInBlock = block.blockNode;
- }
- }else{
- _letBrowserHandle = true;
- }
- }else{
- newrange.setStart(newblock, 0);
- selection.removeAllRanges();
- selection.addRange(newrange);
- if(this.editor.height){
- winUtils.scrollIntoView(newblock);
- }
- if(has("mozilla")){
- // press enter in middle of P may leave a trailing <br/>, let's remove it later
- this._pressedEnterInBlock = block.blockNode;
- }
- }
- }
- return _letBrowserHandle;
- },
- _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){
- // summary:
- // In the case there are multiple text nodes in a row the offset may not be within the node. If the offset is larger than the node length, it will attempt to find
- // the next text sibling until it locates the text node in which the offset refers to
- // node:
- // The node to check.
- // offset:
- // The position to find within the text node
- // tags:
- // private.
- while(node.length < offset && node.nextSibling && node.nextSibling.nodeType==3){
- //Adjust the offset and node in the case of multiple text nodes in a row
- offset = offset - node.length;
- node = node.nextSibling;
- }
- return {"node": node, "offset": offset};
- },
- removeTrailingBr: function(container){
- // summary:
- // If last child of container is a <br>, then remove it.
- // tags:
- // private
- var para = /P|DIV|LI/i.test(container.tagName) ?
- container : selectionapi.getParentOfType(container,['P','DIV','LI']);
- if(!para){ return; }
- if(para.lastChild){
- if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) ||
- para.lastChild.tagName=='BR'){
- domConstruct.destroy(para.lastChild);
- }
- }
- if(!para.childNodes.length){
- para.innerHTML=this.bogusHtmlContent;
- }
- }
- });
- });
- },
- 'dijit/BuxEditor':function(){
- define("dijit/BuxEditor", [
- "dojo/_base/array", // array.forEach
- "dojo/_base/declare", // declare
- "dojo/_base/Deferred", // Deferred
- "dojo/i18n", // i18n.getLocalization
- "dojo/dom-attr", // domAttr.set
- "dojo/dom-class", // domClass.add
- "dojo/dom-geometry",
- "dojo/dom-style", // domStyle.set, get
- "dojo/_base/event", // event.stop
- "dojo/keys", // keys.F1 keys.F15 keys.TAB
- "dojo/_base/lang", // lang.getObject lang.hitch
- "dojo/_base/sniff", // has("ie") has("mac") has("webkit")
- "dojo/string", // string.substitute
- "dojo/topic", // topic.publish()
- "dojo/_base/window", // win.withGlobal
- "./_base/focus", // dijit.getBookmark()
- "./_Container",
- "./Toolbar",
- "./ToolbarSeparator",
- "./layout/_LayoutWidget",
- "./form/ToggleButton",
- "./_editor/_Plugin",
- "./_editor/plugins/BuxEnterKeyHandling",
- "./_editor/html",
- "./_editor/range",
- "./_editor/BuxRichText",
- ".", // dijit._scopeName
- "dojo/i18n!./_editor/nls/commands"
- ], function(array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle,
- event, keys, lang, has, string, topic, win,
- focusBase, _Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton,
- _Plugin, BuxEnterKeyHandling, html, rangeapi, BuxRichText, dijit){
- // module:
- // dijit/BuxEditor
- // summary:
- // A rich text Editing widget
- var Editor = declare("dijit.BuxEditor", BuxRichText, {
- // summary:
- // A rich text Editing widget
- //
- // description:
- // This widget provides basic WYSIWYG editing features, based on the browser's
- // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
- // A plugin model is available to extend the editor's capabilities as well as the
- // the options available in the toolbar. Content generation may vary across
- // browsers, and clipboard operations may have different results, to name
- // a few limitations. Note: this widget should not be used with the HTML
- // <TEXTAREA> tag -- see dijit._editor.BuxRichText for details.
- // plugins: [const] Object[]
- // A list of plugin names (as strings) or instances (as objects)
- // for this widget.
- //
- // When declared in markup, it might look like:
- // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
- plugins: null,
- // extraPlugins: [const] Object[]
- // A list of extra plugin names which will be appended to plugins array
- extraPlugins: null,
- constructor: function(){
- // summary:
- // Runs on widget initialization to setup arrays etc.
- // tags:
- // private
- if(!lang.isArray(this.plugins)){
- this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
- "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
- BuxEnterKeyHandling /*, "createLink"*/];
- }
- this._plugins=[];
- this._editInterval = this.editActionInterval * 1000;
- //IE will always lose focus when other element gets focus, while for FF and safari,
- //when no iframe is used, focus will be lost whenever another element gets focus.
- //For IE, we can connect to onBeforeDeactivate, which will be called right before
- //the focus is lost, so we can obtain the selected range. For other browsers,
- //no equivalent of onBeforeDeactivate, so we need to do two things to make sure
- //selection is properly saved before focus is lost: 1) when user clicks another
- //element in the page, in which case we listen to mousedown on the entire page and
- //see whether user clicks out of a focus editor, if so, save selection (focus will
- //only lost after onmousedown event is fired, so we can obtain correct caret pos.)
- //2) when user tabs away from the editor, which is handled in onKeyDown below.
- if(has("ie") || has("trident")){
- this.events.push("onBeforeDeactivate");
- this.events.push("onBeforeActivate");
- }
- },
- postMixInProperties: function(){
- // summary:
- // Extension to make sure a deferred is in place before certain functions
- // execute, like making sure all the plugins are properly inserted.
- // Set up a deferred so that the value isn't applied to the editor
- // until all the plugins load, needed to avoid timing condition
- // reported in #10537.
- this.setValueDeferred = new Deferred();
- this.inherited(arguments);
- },
- postCreate: function(){
- //for custom undo/redo, if enabled.
- this._steps=this._steps.slice(0);
- this._undoedSteps=this._undoedSteps.slice(0);
- if(lang.isArray(this.extraPlugins)){
- this.plugins=this.plugins.concat(this.extraPlugins);
- }
- this.inherited(arguments);
- this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang);
- if(!this.toolbar){
- // if we haven't been assigned a toolbar, create one
- this.toolbar = new Toolbar({
- dir: this.dir,
- lang: this.lang
- });
- this.header.appendChild(this.toolbar.domNode);
- }
- array.forEach(this.plugins, this.addPlugin, this);
- // Okay, denote the value can now be set.
- this.setValueDeferred.callback(true);
- domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer");
- domClass.add(this.iframe, "dijitEditorIFrame");
- domAttr.set(this.iframe, "allowTransparency", true);
- if(has("webkit")){
- // Disable selecting the entire editor by inadvertent double-clicks.
- // on buttons, title bar, etc. Otherwise clicking too fast on
- // a button such as undo/redo selects the entire editor.
- domStyle.set(this.domNode, "KhtmlUserSelect", "none");
- }
- this.toolbar.startup();
- this.onNormalizedDisplayChanged(); //update toolbar button status
- },
- destroy: function(){
- array.forEach(this._plugins, function(p){
- if(p && p.destroy){
- p.destroy();
- }
- });
- this._plugins=[];
- this.toolbar.destroyRecursive();
- delete this.toolbar;
- this.inherited(arguments);
- },
- addPlugin: function(/*String||Object||Function*/plugin, /*Integer?*/index){
- // summary:
- // takes a plugin name as a string or a plugin instance and
- // adds it to the toolbar and associates it with this editor
- // instance. The resulting plugin is added to the Editor's
- // plugins array. If index is passed, it's placed in the plugins
- // array at that index. No big magic, but a nice helper for
- // passing in plugin names via markup.
- //
- // plugin: String, args object, plugin instance, or plugin constructor
- //
- // args:
- // This object will be passed to the plugin constructor
- //
- // index: Integer
- // Used when creating an instance from
- // something already in this.plugins. Ensures that the new
- // instance is assigned to this.plugins at that index.
- var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin;
- if(!args.setEditor){
- var o={"args":args,"plugin":null,"editor":this};
- if(args.name){
- // search registry for a plugin factory matching args.name, if it's not there then
- // fallback to 1.0 API:
- // ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name)
- // remove fallback for 2.0.
- if(_Plugin.registry[args.name]){
- o.plugin = _Plugin.registry[args.name](args);
- }else{
- topic.publish(dijit._scopeName + ".Editor.getPlugin", o); // publish
- }
- }
- if(!o.plugin){
- var pc = args.ctor || lang.getObject(args.name);
- if(pc){
- o.plugin=new pc(args);
- }
- }
- if(!o.plugin){
- console.warn('Cannot find plugin',plugin);
- return;
- }
- plugin=o.plugin;
- }
- if(arguments.length > 1){
- this._plugins[index] = plugin;
- }else{
- this._plugins.push(plugin);
- }
- plugin.setEditor(this);
- if(lang.isFunction(plugin.setToolbar)){
- plugin.setToolbar(this.toolbar);
- }
- },
- //the following 2 functions are required to make the editor play nice under a layout widget, see #4070
- resize: function(size){
- // summary:
- // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
- if(size){
- // we've been given a height/width for the entire editor (toolbar + contents), calls layout()
- // to split the allocated size between the toolbar and the contents
- _LayoutWidget.prototype.resize.apply(this, arguments);
- }
- /*
- else{
- // do nothing, the editor is already laid out correctly. The user has probably specified
- // the height parameter, which was used to set a size on the iframe
- }
- */
- },
- layout: function(){
- // summary:
- // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly
- // tags:
- // protected
- // Converts the iframe (or rather the <div> surrounding it) to take all the available space
- // except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
- // A class was added to the iframe container and some themes style it, so we have to
- // calc off the added margins and padding too. See tracker: #10662
- var areaHeight = (this._contentBox.h -
- (this.getHeaderHeight() + this.getFooterHeight() +
- domGeometry.getPadBorderExtents(this.iframe.parentNode).h +
- domGeometry.getMarginExtents(this.iframe.parentNode).h));
- this.editingArea.style.height = areaHeight + "px";
- if(this.iframe){
- this.iframe.style.height="100%";
- }
- this._layoutMode = true;
- },
- _onIEMouseDown: function(/*Event*/ e){
- // summary:
- // IE only to prevent 2 clicks to focus
- // tags:
- // private
- var outsideClientArea;
- // IE 8's componentFromPoint is broken, which is a shame since it
- // was smaller code, but oh well. We have to do this brute force
- // to detect if the click was scroller or not.
- var b = this.document.body;
- var clientWidth = b.clientWidth;
- var clientHeight = b.clientHeight;
- var clientLeft = b.clientLeft;
- var offsetWidth = b.offsetWidth;
- var offsetHeight = b.offsetHeight;
- var offsetLeft = b.offsetLeft;
- //Check for vertical scroller click.
- if(/^rtl$/i.test(b.dir || "")){
- if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
- // Check the click was between width and offset width, if so, scroller
- outsideClientArea = true;
- }
- }else{
- // RTL mode, we have to go by the left offsets.
- if(e.x < clientLeft && e.x > offsetLeft){
- // Check the click was between width and offset width, if so, scroller
- outsideClientArea = true;
- }
- }
- if(!outsideClientArea){
- // Okay, might be horiz scroller, check that.
- if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
- // Horizontal scroller.
- outsideClientArea = true;
- }
- }
- if(!outsideClientArea){
- delete this._cursorToStart; // Remove the force to cursor to start position.
- delete this._savedSelection; // new mouse position overrides old selection
- if(e.target.tagName == "BODY"){
- setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0);
- }
- this.inherited(arguments);
- }
- },
- onBeforeActivate: function(){
- this._restoreSelection();
- },
- onBeforeDeactivate: function(e){
- // summary:
- // Called on IE right before focus is lost. Saves the selected range.
- // tags:
- // private
- if(this.customUndo){
- this.endEditing(true);
- }
- //in IE, the selection will be lost when other elements get focus,
- //let's save focus before the editor is deactivated
- if(e.target.tagName != "BODY"){
- this._saveSelection();
- }
- //console.log('onBeforeDeactivate',this);
- },
- /* beginning of custom undo/redo support */
- // customUndo: Boolean
- // Whether we shall use custom undo/redo support instead of the native
- // browser support. By default, we now use custom undo. It works better
- // than native browser support and provides a consistent behavior across
- // browsers with a minimal performance hit. We already had the hit on
- // the slowest browser, IE, anyway.
- customUndo: true,
- // editActionInterval: Integer
- // When using customUndo, not every keystroke will be saved as a step.
- // Instead typing (including delete) will be grouped together: after
- // a user stops typing for editActionInterval seconds, a step will be
- // saved; if a user resume typing within editActionInterval seconds,
- // the timeout will be restarted. By default, editActionInterval is 3
- // seconds.
- editActionInterval: 3,
- beginEditing: function(cmd){
- // summary:
- // Called to note that the user has started typing alphanumeric characters, if it's not already noted.
- // Deals with saving undo; see editActionInterval parameter.
- // tags:
- // private
- if(!this._inEditing){
- this._inEditing=true;
- this._beginEditing(cmd);
- }
- if(this.editActionInterval>0){
- if(this._editTimer){
- clearTimeout(this._editTimer);
- }
- this._editTimer = setTimeout(lang.hitch(this, this.endEditing), this._editInterval);
- }
- },
- // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
- _steps:[],
- _undoedSteps:[],
- execCommand: function(cmd){
- // summary:
- // Main handler for executing any commands to the editor, like paste, bold, etc.
- // Called by plugins, but not meant to be called by end users.
- // tags:
- // protected
- if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
- return this[cmd]();
- }else{
- if(this.customUndo){
- this.endEditing();
- this._beginEditing();
- }
- var r = this.inherited(arguments);
- if(this.customUndo){
- this._endEditing();
- }
- return r;
- }
- },
- _pasteImpl: function(){
- // summary:
- // Over-ride of paste command control to make execCommand cleaner
- // tags:
- // Protected
- return this._clipboardCommand("paste");
- },
- _cutImpl: function(){
- // summary:
- // Over-ride of cut command control to make execCommand cleaner
- // tags:
- // Protected
- return this._clipboardCommand("cut");
- },
- _copyImpl: function(){
- // summary:
- // Over-ride of copy command control to make execCommand cleaner
- // tags:
- // Protected
- return this._clipboardCommand("copy");
- },
- _clipboardCommand: function(cmd){
- // summary:
- // Function to handle processing clipboard commands (or at least try to).
- // tags:
- // Private
- var r;
- try{
- // Try to exec the superclass exec-command and see if it works.
- r = this.document.execCommand(cmd, false, null);
- if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js
- throw { code: 1011 }; // throw an object like Mozilla's error
- }
- }catch(e){
- //TODO: when else might we get an exception? Do we need the Mozilla test below?
- if(e.code == 1011 /* Mozilla: service denied */){
- // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136
- var sub = string.substitute,
- accel = {cut:'X', copy:'C', paste:'V'};
- alert(sub(this.commands.systemShortcut,
- [this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
- }
- r = false;
- }
- return r;
- },
- queryCommandEnabled: function(cmd){
- // summary:
- // Returns true if specified editor command is enabled.
- // Used by the plugins to know when to highlight/not highlight buttons.
- // tags:
- // protected
- if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
- return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
- }else{
- return this.inherited(arguments);
- }
- },
- _moveToBookmark: function(b){
- // summary:
- // Selects the text specified in bookmark b
- // tags:
- // private
- var bookmark = b.mark;
- var mark = b.mark;
- var col = b.isCollapsed;
- var r, sNode, eNode, sel;
- if(mark){
- if(has("ie") < 9){
- if(lang.isArray(mark)){
- //IE CONTROL, have to use the native bookmark.
- bookmark = [];
- array.forEach(mark,function(n){
- bookmark.push(rangeapi.getNode(n,this.editNode));
- },this);
- win.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]);
- }else{
- if(mark.startContainer && mark.endContainer){
- // Use the pseudo WC3 range API. This works better for positions
- // than the IE native bookmark code.
- sel = rangeapi.getSelection(this.window);
- if(sel && sel.removeAllRanges){
- sel.removeAllRanges();
- r = rangeapi.create(this.window);
- sNode = rangeapi.getNode(mark.startContainer,this.editNode);
- eNode = rangeapi.getNode(mark.endContainer,this.editNode);
- if(sNode && eNode){
- // Okay, we believe we found the position, so add it into the selection
- // There are cases where it may not be found, particularly in undo/redo, when
- // IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
- // So, in those cases, don't bother restoring selection.
- r.setStart(sNode,mark.startOffset);
- r.setEnd(eNode,mark.endOffset);
- sel.addRange(r);
- }
- }
- }
- }
- }else{//w3c range
- sel = rangeapi.getSelection(this.window);
- if(sel && sel.removeAllRanges){
- sel.removeAllRanges();
- r = rangeapi.create(this.window);
- sNode = rangeapi.getNode(mark.startContainer,this.editNode);
- eNode = rangeapi.getNode(mark.endContainer,this.editNode);
- if(sNode && eNode){
- // Okay, we believe we found the position, so add it into the selection
- // There are cases where it may not be found, particularly in undo/redo, when
- // formatting as been done and so on, so don't restore selection then.
- r.setStart(sNode,mark.startOffset);
- r.setEnd(eNode,mark.endOffset);
- sel.addRange(r);
- }
- }
- }
- }
- },
- _changeToStep: function(from, to){
- // summary:
- // Reverts editor to "to" setting, from the undo stack.
- // tags:
- // private
- this.setValue(to.text);
- var b=to.bookmark;
- if(!b){ return; }
- this._moveToBookmark(b);
- },
- undo: function(){
- // summary:
- // Handler for editor undo (ex: ctrl-z) operation
- // tags:
- // private
- //console.log('undo');
- var ret = false;
- if(!this._undoRedoActive){
- this._undoRedoActive = true;
- this.endEditing(true);
- var s=this._steps.pop();
- if(s && this._steps.length>0){
- this.focus();
- this._changeToStep(s,this._steps[this._steps.length-1]);
- this._undoedSteps.push(s);
- this.onDisplayChanged();
- delete this._undoRedoActive;
- ret = true;
- }
- delete this._undoRedoActive;
- }
- return ret;
- },
- redo: function(){
- // summary:
- // Handler for editor redo (ex: ctrl-y) operation
- // tags:
- // private
- //console.log('redo');
- var ret = false;
- if(!this._undoRedoActive){
- this._undoRedoActive = true;
- this.endEditing(true);
- var s=this._undoedSteps.pop();
- if(s && this._steps.length>0){
- this.focus();
- this._changeToStep(this._steps[this._steps.length-1],s);
- this._steps.push(s);
- this.onDisplayChanged();
- ret = true;
- }
- delete this._undoRedoActive;
- }
- return ret;
- },
- endEditing: function(ignore_caret){
- // summary:
- // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
- // Deals with saving undo; see editActionInterval parameter.
- // tags:
- // private
- if(this._editTimer){
- clearTimeout(this._editTimer);
- }
- if(this._inEditing){
- this._endEditing(ignore_caret);
- this._inEditing=false;
- }
- },
- _getBookmark: function(){
- // summary:
- // Get the currently selected text
- // tags:
- // protected
- var b=win.withGlobal(this.window,focusBase.getBookmark);
- var tmp=[];
- if(b && b.mark){
- var mark = b.mark;
- if(has("ie") < 9){
- // Try to use the pseudo range API on IE for better accuracy.
- var sel = rangeapi.getSelection(this.window);
- if(!lang.isArray(mark)){
- if(sel){
- var range;
- if(sel.rangeCount){
- range = sel.getRangeAt(0);
- }
- if(range){
- b.mark = range.cloneRange();
- }else{
- b.mark = win.withGlobal(this.window,focusBase.getBookmark);
- }
- }
- }else{
- // Control ranges (img, table, etc), handle differently.
- array.forEach(b.mark,function(n){
- tmp.push(rangeapi.getIndex(n,this.editNode).o);
- },this);
- b.mark = tmp;
- }
- }
- try{
- if(b.mark && b.mark.startContainer){
- tmp=rangeapi.getIndex(b.mark.startContainer,this.editNode).o;
- b.mark={startContainer:tmp,
- startOffset:b.mark.startOffset,
- endContainer:b.mark.endContainer===b.mark.startContainer?tmp:rangeapi.getIndex(b.mark.endContainer,this.editNode).o,
- endOffset:b.mark.endOffset};
- }
- }catch(e){
- b.mark = null;
- }
- }
- return b;
- },
- _beginEditing: function(){
- // summary:
- // Called when the user starts typing alphanumeric characters.
- // Deals with saving undo; see editActionInterval parameter.
- // tags:
- // private
- if(this._steps.length === 0){
- // You want to use the editor content without post filtering
- // to make sure selection restores right for the 'initial' state.
- // and undo is called. So not using this.value, as it was 'processed'
- // and the line-up for selections may have been altered.
- this._steps.push({'text':html.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()});
- }
- },
- _endEditing: function(){
- // summary:
- // Called when the user stops typing alphanumeric characters.
- // Deals with saving undo; see editActionInterval parameter.
- // tags:
- // private
- // Avoid filtering to make sure selections restore.
- var v = html.getChildrenHtml(this.editNode);
- this._undoedSteps=[];//clear undoed steps
- this._steps.push({text: v, bookmark: this._getBookmark()});
- },
- onKeyDown: function(e){
- // summary:
- // Handler for onkeydown event.
- // tags:
- // private
- //We need to save selection if the user TAB away from this editor
- //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
- if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){
- this._saveSelection();
- }
- if(!this.customUndo){
- this.inherited(arguments);
- return;
- }
- var k = e.keyCode;
- if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
- if(k == 90 || k == 122){ //z
- event.stop(e);
- this.undo();
- return;
- }else if(k == 89 || k == 121){ //y
- event.stop(e);
- this.redo();
- return;
- }
- }
- this.inherited(arguments);
- switch(k){
- case keys.ENTER:
- case keys.BACKSPACE:
- case keys.DELETE:
- this.beginEditing();
- break;
- case 88: //x
- case 86: //v
- if(e.ctrlKey && !e.altKey && !e.metaKey){
- this.endEditing();//end current typing step if any
- if(e.keyCode == 88){
- this.beginEditing('cut');
- //use timeout to trigger after the cut is complete
- setTimeout(lang.hitch(this, this.endEditing), 1);
- }else{
- this.beginEditing('paste');
- //use timeout to trigger after the paste is complete
- setTimeout(lang.hitch(this, this.endEditing), 1);
- }
- break;
- }
- //pass through
- default:
- if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<keys.F1 || e.keyCode>keys.F15)){
- this.beginEditing();
- break;
- }
- //pass through
- case keys.ALT:
- this.endEditing();
- break;
- case keys.UP_ARROW:
- case keys.DOWN_ARROW:
- case keys.LEFT_ARROW:
- case keys.RIGHT_ARROW:
- case keys.HOME:
- case keys.END:
- case keys.PAGE_UP:
- case keys.PAGE_DOWN:
- this.endEditing(true);
- break;
- //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
- case keys.CTRL:
- case keys.SHIFT:
- case keys.TAB:
- break;
- }
- },
- _onBlur: function(){
- // summary:
- // Called from focus manager when focus has moved away from this editor
- // tags:
- // protected
- //this._saveSelection();
- this.inherited(arguments);
- this.endEditing(true);
- },
- _saveSelection: function(){
- // summary:
- // Save the currently selected text in _savedSelection attribute
- // tags:
- // private
- try{
- this._savedSelection=this._getBookmark();
- }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */}
- },
- _restoreSelection: function(){
- // summary:
- // Re-select the text specified in _savedSelection attribute;
- // see _saveSelection().
- // tags:
- // private
- if(this._savedSelection){
- // Clear off cursor to start, we're deliberately going to a selection.
- delete this._cursorToStart;
- // only restore the selection if the current range is collapsed
- // if not collapsed, then it means the editor does not lose
- // selection and there is no need to restore it
- if(win.withGlobal(this.window,'isCollapsed',dijit)){
- this._moveToBookmark(this._savedSelection);
- }
- delete this._savedSelection;
- }
- },
- onClick: function(){
- // summary:
- // Handler for when editor is clicked
- // tags:
- // protected
- this.endEditing(true);
- this.inherited(arguments);
- },
- replaceValue: function(/*String*/ html){
- // summary:
- // over-ride of replaceValue to support custom undo and stack maintenance.
- // tags:
- // protected
- if(!this.customUndo){
- this.inherited(arguments);
- }else{
- if(this.isClosed){
- this.setValue(html);
- }else{
- this.beginEditing();
- if(!html){
- html = " "; //
- }
- this.setValue(html);
- this.endEditing();
- }
- }
- },
- _setDisabledAttr: function(/*Boolean*/ value){
- var disableFunc = lang.hitch(this, function(){
- if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
- // Disable editor: disable all enabled buttons and remember that list
- array.forEach(this._plugins, function(p){
- p.set("disabled", true);
- });
- }else if(this.disabled && !value){
- // Restore plugins to being active.
- array.forEach(this._plugins, function(p){
- p.set("disabled", false);
- });
- }
- });
- this.setValueDeferred.addCallback(disableFunc);
- this.inherited(arguments);
- },
- _setStateClass: function(){
- try{
- this.inherited(arguments);
- // Let theme set the editor's text color based on editor enabled/disabled state.
- // We need to jump through hoops because the main document (where the theme CSS is)
- // is separate from the iframe's document.
- if(this.document && this.document.body){
- domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color"));
- }
- }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */}
- }
- });
- // Register the "default plugins", ie, the built-in editor commands
- function simplePluginFactory(args){
- return new _Plugin({ command: args.name });
- }
- function togglePluginFactory(args){
- return new _Plugin({ buttonClass: ToggleButton, command: args.name });
- }
- lang.mixin(_Plugin.registry, {
- "undo": simplePluginFactory,
- "redo": simplePluginFactory,
- "cut": simplePluginFactory,
- "copy": simplePluginFactory,
- "paste": simplePluginFactory,
- "insertOrderedList": simplePluginFactory,
- "insertUnorderedList": simplePluginFactory,
- "indent": simplePluginFactory,
- "outdent": simplePluginFactory,
- "justifyCenter": simplePluginFactory,
- "justifyFull": simplePluginFactory,
- "justifyLeft": simplePluginFactory,
- "justifyRight": simplePluginFactory,
- "delete": simplePluginFactory,
- "selectAll": simplePluginFactory,
- "removeFormat": simplePluginFactory,
- "unlink": simplePluginFactory,
- "insertHorizontalRule": simplePluginFactory,
- "bold": togglePluginFactory,
- "italic": togglePluginFactory,
- "underline": togglePluginFactory,
- "strikethrough": togglePluginFactory,
- "subscript": togglePluginFactory,
- "superscript": togglePluginFactory,
- "|": function(){
- return new _Plugin({ button: new ToolbarSeparator(), setEditor: function(editor){this.editor = editor;}});
- }
- });
- return Editor;
- });
- },
- 'dijit/_editor/_Plugin':function(){
- define("dijit/_editor/_Plugin", [
- "dojo/_base/connect", // connect.connect
- "dojo/_base/declare", // declare
- "dojo/_base/lang", // lang.mixin, lang.hitch
- "../form/Button"
- ], function(connect, declare, lang, Button){
- // module:
- // dijit/_editor/_Plugin
- // summary:
- // Base class for a "plugin" to the editor, which is usually
- // a single button on the Toolbar and some associated code
- var _Plugin = declare("dijit._editor._Plugin", null, {
- // summary:
- // Base class for a "plugin" to the editor, which is usually
- // a single button on the Toolbar and some associated code
- constructor: function(/*Object?*/args){
- this.params = args || {};
- lang.mixin(this, this.params);
- this._connects=[];
- this._attrPairNames = {};
- },
- // editor: [const] dijit.Editor
- // Points to the parent editor
- editor: null,
- // iconClassPrefix: [const] String
- // The CSS class name for the button node is formed from `iconClassPrefix` and `command`
- iconClassPrefix: "dijitEditorIcon",
- // button: dijit._Widget?
- // Pointer to `dijit.form.Button` or other widget (ex: `dijit.form.FilteringSelect`)
- // that is added to the toolbar to control this plugin.
- // If not specified, will be created on initialization according to `buttonClass`
- button: null,
- // command: String
- // String like "insertUnorderedList", "outdent", "justifyCenter", etc. that represents an editor command.
- // Passed to editor.execCommand() if `useDefaultCommand` is true.
- command: "",
- // useDefaultCommand: Boolean
- // If true, this plugin executes by calling Editor.execCommand() with the argument specified in `command`.
- useDefaultCommand: true,
- // buttonClass: Widget Class
- // Class of widget (ex: dijit.form.Button or dijit.form.FilteringSelect)
- // that is added to the toolbar to control this plugin.
- // This is used to instantiate the button, unless `button` itself is specified directly.
- buttonClass: Button,
- // disabled: Boolean
- // Flag to indicate if this plugin has been disabled and should do nothing
- // helps control button state, among other things. Set via the setter api.
- disabled: false,
- getLabel: function(/*String*/key){
- // summary:
- // Returns the label to use for the button
- // tags:
- // private
- return this.editor.commands[key]; // String
- },
- _initButton: function(){
- // summary:
- // Initialize the button or other widget that will control this plugin.
- // This code only works for plugins controlling built-in commands in the editor.
- // tags:
- // protected extension
- if(this.command.length){
- var label = this.getLabel(this.command),
- editor = this.editor,
- className = this.iconClassPrefix+" "+this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1);
- if(!this.button){
- var props = lang.mixin({
- label: label,
- dir: editor.dir,
- lang: editor.lang,
- showLabel: false,
- iconClass: className,
- dropDown: this.dropDown,
- tabIndex: "-1"
- }, this.params || {});
- this.button = new this.buttonClass(props);
- }
- }
- if(this.get("disabled") && this.button){
- this.button.set("disabled", this.get("disabled"));
- }
- },
- destroy: function(){
- // summary:
- // Destroy this plugin
- var h;
- while(h = this._connects.pop()){ h.remove(); }
- if(this.dropDown){
- this.dropDown.destroyRecursive();
- }
- },
- connect: function(o, f, tf){
- // summary:
- // Make a connect.connect() that is automatically disconnected when this plugin is destroyed.
- // Similar to `dijit._Widget.connect`.
- // tags:
- // protected
- this._connects.push(connect.connect(o, f, this, tf));
- },
- updateState: function(){
- // summary:
- // Change state of the plugin to respond to events in the editor.
- // description:
- // This is called on meaningful events in the editor, such as change of selection
- // or caret position (but not simple typing of alphanumeric keys). It gives the
- // plugin a chance to update the CSS of its button.
- //
- // For example, the "bold" plugin will highlight/unhighlight the bold button depending on whether the
- // characters next to the caret are bold or not.
- //
- // Only makes sense when `useDefaultCommand` is true, as it calls Editor.queryCommandEnabled(`command`).
- var e = this.editor,
- c = this.command,
- checked, enabled;
- if(!e || !e.isLoaded || !c.length){ return; }
- var disabled = this.get("disabled");
- if(this.button){
- try{
- enabled = !disabled && e.queryCommandEnabled(c);
- if(this.enabled !== enabled){
- this.enabled = enabled;
- this.button.set('disabled', !enabled);
- }
- if(typeof this.button.checked == 'boolean'){
- checked = e.queryCommandState(c);
- if(this.checked !== checked){
- this.checked = checked;
- this.button.set('checked', e.queryCommandState(c));
- }
- }
- }catch(e){
- console.log(e); // FIXME: we shouldn't have debug statements in our code. Log as an error?
- }
- }
- },
- setEditor: function(/*dijit.Editor*/ editor){
- // summary:
- // Tell the plugin which Editor it is associated with.
- // TODO: refactor code to just pass editor to constructor.
- // FIXME: detach from previous editor!!
- this.editor = editor;
- // FIXME: prevent creating this if we don't need to (i.e., editor can't handle our command)
- this._initButton();
- // Processing for buttons that execute by calling editor.execCommand()
- if(this.button && this.useDefaultCommand){
- if(this.editor.queryCommandAvailable(this.command)){
- this.connect(this.button, "onClick",
- lang.hitch(this.editor, "execCommand", this.command, this.commandArg)
- );
- }else{
- // hide button because editor doesn't support command (due to browser limitations)
- this.button.domNode.style.display = "none";
- }
- }
- this.connect(this.editor, "onNormalizedDisplayChanged", "updateState");
- },
- setToolbar: function(/*dijit.Toolbar*/ toolbar){
- // summary:
- // Tell the plugin to add it's controller widget (often a button)
- // to the toolbar. Does nothing if there is no controller widget.
- // TODO: refactor code to just pass toolbar to constructor.
- if(this.button){
- toolbar.addChild(this.button);
- }
- // console.debug("adding", this.button, "to:", toolbar);
- },
- set: function(/* attribute */ name, /* anything */ value){
- // summary:
- // Set a property on a plugin
- // name:
- // The property to set.
- // value:
- // The value to set in the property.
- // description:
- // Sets named properties on a plugin which may potentially be handled by a
- // setter in the plugin.
- // For example, if the plugin has a properties "foo"
- // and "bar" and a method named "_setFooAttr", calling:
- // | plugin.set("foo", "Howdy!");
- // would be equivalent to writing:
- // | plugin._setFooAttr("Howdy!");
- // and:
- // | plugin.set("bar", 3);
- // would be equivalent to writing:
- // | plugin.bar = 3;
- //
- // set() may also be called with a hash of name/value pairs, ex:
- // | plugin.set({
- // | foo: "Howdy",
- // | bar: 3
- // | })
- // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
- if(typeof name === "object"){
- for(var x in name){
- this.set(x, name[x]);
- }
- return this;
- }
- var names = this._getAttrNames(name);
- if(this[names.s]){
- // use the explicit setter
- var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1));
- }else{
- this._set(name, value);
- }
- return result || this;
- },
- get: function(name){
- // summary:
- // Get a property from a plugin.
- // name:
- // The property to get.
- // description:
- // Get a named property from a plugin. The property may
- // potentially be retrieved via a getter method. If no getter is defined, this
- // just retrieves the object's property.
- // For example, if the plugin has a properties "foo"
- // and "bar" and a method named "_getFooAttr", calling:
- // | plugin.get("foo");
- // would be equivalent to writing:
- // | plugin._getFooAttr();
- // and:
- // | plugin.get("bar");
- // would be equivalent to writing:
- // | plugin.bar;
- var names = this._getAttrNames(name);
- return this[names.g] ? this[names.g]() : this[name];
- },
- _setDisabledAttr: function(disabled){
- // summary:
- // Function to set the plugin state and call updateState to make sure the
- // button is updated appropriately.
- this.disabled = disabled;
- this.updateState();
- },
- _getAttrNames: function(name){
- // summary:
- // Helper function for get() and set().
- // Caches attribute name values so we don't do the string ops every time.
- // tags:
- // private
- var apn = this._attrPairNames;
- if(apn[name]){ return apn[name]; }
- var uc = name.charAt(0).toUpperCase() + name.substr(1);
- return (apn[name] = {
- s: "_set"+uc+"Attr",
- g: "_get"+uc+"Attr"
- });
- },
- _set: function(/*String*/ name, /*anything*/ value){
- // summary:
- // Helper function to set new value for specified attribute
- this[name] = value;
- }
- });
- // Hash mapping plugin name to factory, used for registering plugins
- _Plugin.registry = {};
- return _Plugin;
- });
- },
- 'dijit/_editor/plugins/FontChoice':function(){
- define("dijit/_editor/plugins/FontChoice", [
- "dojo/_base/array", // array.indexOf array.map
- "dojo/_base/declare", // declare
- "dojo/dom-construct", // domConstruct.place
- "dojo/i18n", // i18n.getLocalization
- "dojo/_base/lang", // lang.delegate lang.hitch lang.isString
- "dojo/store/Memory", // MemoryStore
- "dojo/_base/window", // win.withGlobal
- "../../registry", // registry.getUniqueId
- "../../_Widget",
- "../../_TemplatedMixin",
- "../../_WidgetsInTemplateMixin",
- "../../form/FilteringSelect",
- "../_Plugin",
- "../range",
- "../selection",
- "dojo/i18n!../nls/FontChoice"
- ], function(array, declare, domConstruct, i18n, lang, MemoryStore, win,
- registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi, selectionapi){
- /*=====
- var _Plugin = dijit._editor._Plugin;
- var _Widget = dijit._Widget;
- var _TemplatedMixin = dijit._TemplatedMixin;
- var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin;
- var FilteringSelect = dijit.form.FilteringSelect;
- =====*/
- // module:
- // dijit/_editor/plugins/FontChoice
- // summary:
- // fontchoice, fontsize, and formatblock editor plugins
- var _FontDropDown = declare("dijit._editor.plugins._FontDropDown",
- [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
- // summary:
- // Base class for widgets that contains a label (like "Font:")
- // and a FilteringSelect drop down to pick a value.
- // Used as Toolbar entry.
- // label: [public] String
- // The label to apply to this particular FontDropDown.
- label: "",
- // plainText: [public] boolean
- // Flag to indicate that the returned label should be plain text
- // instead of an example.
- plainText: false,
- // templateString: [public] String
- // The template used to construct the labeled dropdown.
- templateString:
- "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" +
- "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" +
- "<input data-dojo-type='dijit.form.FilteringSelect' required='false' " +
- "data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " +
- "tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" +
- "</span>",
- postMixInProperties: function(){
- // summary:
- // Over-ride to set specific properties.
- this.inherited(arguments);
- this.strings = i18n.getLocalization("dijit._editor", "FontChoice");
- // Set some substitution variables used in the template
- this.label = this.strings[this.command];
- this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_")); // TODO: unneeded??
- this.selectId = this.id + "_select"; // used in template
- this.inherited(arguments);
- },
- postCreate: function(){
- // summary:
- // Over-ride for the default postCreate action
- // This establishes the filtering selects and the like.
- // Initialize the list of items in the drop down by creating data store with items like:
- // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" }
- this.select.set("store", new MemoryStore({
- idProperty: "value",
- data: array.map(this.values, function(value){
- var name = this.strings[value] || value;
- return {
- label: this.getLabel(value, name),
- name: name,
- value: value
- };
- }, this)
- }));
- this.select.set("value", "", false);
- this.disabled = this.select.get("disabled");
- },
- _setValueAttr: function(value, priorityChange){
- // summary:
- // Over-ride for the default action of setting the
- // widget value, maps the input to known values
- // value: Object|String
- // The value to set in the select.
- // priorityChange:
- // Optional parameter used to tell the select whether or not to fire
- // onChange event.
- // if the value is not a permitted value, just set empty string to prevent showing the warning icon
- priorityChange = priorityChange !== false;
- this.select.set('value', array.indexOf(this.values,value) < 0 ? "" : value, priorityChange);
- if(!priorityChange){
- // Clear the last state in case of updateState calls. Ref: #10466
- this.select._lastValueReported=null;
- }
- },
- _getValueAttr: function(){
- // summary:
- // Allow retrieving the value from the composite select on
- // call to button.get("value");
- return this.select.get('value');
- },
- focus: function(){
- // summary:
- // Over-ride for focus control of this widget. Delegates focus down to the
- // filtering select.
- this.select.focus();
- },
- _setDisabledAttr: function(value){
- // summary:
- // Over-ride for the button's 'disabled' attribute so that it can be
- // disabled programmatically.
- // Save off ths disabled state so the get retrieves it correctly
- //without needing to have a function proxy it.
- this.disabled = value;
- this.select.set("disabled", value);
- }
- });
- var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, {
- // summary:
- // Dropdown to select a font; goes in editor toolbar.
- // generic: Boolean
- // Use generic (web standard) font names
- generic: false,
- // command: [public] String
- // The editor 'command' implemented by this plugin.
- command: "fontName",
- postMixInProperties: function(){
- // summary:
- // Over-ride for the default posr mixin control
- if(!this.values){
- this.values = this.generic ?
- ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics
- ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"];
- }
- this.inherited(arguments);
- },
- getLabel: function(value, name){
- // summary:
- // Function used to generate the labels of the format dropdown
- // will return a formatted, or plain label based on the value
- // of the plainText option.
- // value: String
- // The 'insert value' associated with a name
- // name: String
- // The text name of the value
- if(this.plainText){
- return name;
- }else{
- return "<div style='font-family: "+value+"'>" + name + "</div>";
- }
- },
- _setValueAttr: function(value, priorityChange){
- // summary:
- // Over-ride for the default action of setting the
- // widget value, maps the input to known values
- priorityChange = priorityChange !== false;
- if(this.generic){
- var map = {
- "Arial": "sans-serif",
- "Helvetica": "sans-serif",
- "Myriad": "sans-serif",
- "Times": "serif",
- "Times New Roman": "serif",
- "Comic Sans MS": "cursive",
- "Apple Chancery": "cursive",
- "Courier": "monospace",
- "Courier New": "monospace",
- "Papyrus": "fantasy",
- "Estrangelo Edessa": "cursive", // Windows 7
- "Gabriola": "fantasy" // Windows 7
- };
- value = map[value] || value;
- }
- this.inherited(arguments, [value, priorityChange]);
- }
- });
- var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, {
- // summary:
- // Dropdown to select a font size; goes in editor toolbar.
- // command: [public] String
- // The editor 'command' implemented by this plugin.
- command: "fontSize",
- // values: [public] Number[]
- // The HTML font size values supported by this plugin
- values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE
- getLabel: function(value, name){
- // summary:
- // Function used to generate the labels of the format dropdown
- // will return a formatted, or plain label based on the value
- // of the plainText option.
- // We're stuck using the deprecated FONT tag to correspond
- // with the size measurements used by the editor
- // value: String
- // The 'insert value' associated with a name
- // name: String
- // The text name of the value
- if(this.plainText){
- return name;
- }else{
- return "<font size=" + value + "'>" + name + "</font>";
- }
- },
- _setValueAttr: function(value, priorityChange){
- // summary:
- // Over-ride for the default action of setting the
- // widget value, maps the input to known values
- priorityChange = priorityChange !== false;
- if(value.indexOf && value.indexOf("px") != -1){
- var pixels = parseInt(value, 10);
- value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value;
- }
- this.inherited(arguments, [value, priorityChange]);
- }
- });
- var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, {
- // summary:
- // Dropdown to select a format (like paragraph or heading); goes in editor toolbar.
- // command: [public] String
- // The editor 'command' implemented by this plugin.
- command: "formatBlock",
- // values: [public] Array
- // The HTML format tags supported by this plugin
- values: ["noFormat", "p", "h1", "h2", "h3", "pre"],
- postCreate: function(){
- // Init and set the default value to no formatting. Update state will adjust it
- // as needed.
- this.inherited(arguments);
- this.set("value", "noFormat", false);
- },
- getLabel: function(value, name){
- // summary:
- // Function used to generate the labels of the format dropdown
- // will return a formatted, or plain label based on the value
- // of the plainText option.
- // value: String
- // The 'insert value' associated with a name
- // name: String
- // The text name of the value
- if(this.plainText || value == "noFormat"){
- return name;
- }else{
- return "<" + value + ">" + name + "</" + value + ">";
- }
- },
- _execCommand: function(editor, command, choice){
- // summary:
- // Over-ride for default exec-command label.
- // Allows us to treat 'none' as special.
- if(choice === "noFormat"){
- var start;
- var end;
- var sel = rangeapi.getSelection(editor.window);
- if(sel && sel.rangeCount > 0){
- var range = sel.getRangeAt(0);
- var node, tag;
- if(range){
- start = range.startContainer;
- end = range.endContainer;
- // find containing nodes of start/end.
- while(start && start !== editor.editNode &&
- start !== editor.document.body &&
- start.nodeType !== 1){
- start = start.parentNode;
- }
- while(end && end !== editor.editNode &&
- end !== editor.document.body &&
- end.nodeType !== 1){
- end = end.parentNode;
- }
- var processChildren = lang.hitch(this, function(node, ary){
- if(node.childNodes && node.childNodes.length){
- var i;
- for(i = 0; i < node.childNodes.length; i++){
- var c = node.childNodes[i];
- if(c.nodeType == 1){
- if(win.withGlobal(editor.window, "inSelection", selectionapi, [c])){
- var tag = c.tagName? c.tagName.toLowerCase(): "";
- if(array.indexOf(this.values, tag) !== -1){
- ary.push(c);
- }
- processChildren(c, ary);
- }
- }
- }
- }
- });
- var unformatNodes = lang.hitch(this, function(nodes){
- // summary:
- // Internal function to clear format nodes.
- // nodes:
- // The array of nodes to strip formatting from.
- if(nodes && nodes.length){
- editor.beginEditing();
- while(nodes.length){
- this._removeFormat(editor, nodes.pop());
- }
- editor.endEditing();
- }
- });
- var clearNodes = [];
- if(start == end){
- //Contained within the same block, may be collapsed, but who cares, see if we
- // have a block element to remove.
- var block;
- node = start;
- while(node && node !== editor.editNode && node !== editor.document.body){
- if(node.nodeType == 1){
- tag = node.tagName? node.tagName.toLowerCase(): "";
- if(array.indexOf(this.values, tag) !== -1){
- block = node;
- break;
- }
- }
- node = node.parentNode;
- }
- //Also look for all child nodes in the selection that may need to be
- //cleared of formatting
- processChildren(start, clearNodes);
- if(block){ clearNodes = [block].concat(clearNodes); }
- unformatNodes(clearNodes);
- }else{
- // Probably a multi select, so we have to process it. Whee.
- node = start;
- while(win.withGlobal(editor.window, "inSelection", selectionapi, [node])){
- if(node.nodeType == 1){
- tag = node.tagName? node.tagName.toLowerCase(): "";
- if(array.indexOf(this.values, tag) !== -1){
- clearNodes.push(node);
- }
- processChildren(node,clearNodes);
- }
- node = node.nextSibling;
- }
- unformatNodes(clearNodes);
- }
- editor.onDisplayChanged();
- }
- }
- }else{
- editor.execCommand(command, choice);
- }
- },
- _removeFormat: function(editor, node){
- // summary:
- // function to remove the block format node.
- // node:
- // The block format node to remove (and leave the contents behind)
- if(editor.customUndo){
- // So of course IE doesn't work right with paste-overs.
- // We have to do this manually, which is okay since IE already uses
- // customUndo and we turned it on for WebKit. WebKit pasted funny,
- // so couldn't use the execCommand approach
- while(node.firstChild){
- domConstruct.place(node.firstChild, node, "before");
- }
- node.parentNode.removeChild(node);
- }else{
- // Everyone else works fine this way, a paste-over and is native
- // undo friendly.
- win.withGlobal(editor.window,
- "selectElementChildren", selectionapi, [node]);
- var html = win.withGlobal(editor.window,
- "getSelectedHtml", selectionapi, [null]);
- win.withGlobal(editor.window,
- "selectElement", selectionapi, [node]);
- editor.execCommand("inserthtml", html||"");
- }
- }
- });
- // TODO: for 2.0, split into FontChoice plugin into three separate classes,
- // one for each command (and change registry below)
- var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin,{
- // summary:
- // This plugin provides three drop downs for setting style in the editor
- // (font, font size, and format block), as controlled by command.
- //
- // description:
- // The commands provided by this plugin are:
- //
- // * fontName
- // | Provides a drop down to select from a list of font names
- // * fontSize
- // | Provides a drop down to select from a list of font sizes
- // * formatBlock
- // | Provides a drop down to select from a list of block styles
- // |
- //
- // which can easily be added to an editor by including one or more of the above commands
- // in the `plugins` attribute as follows:
- //
- // | plugins="['fontName','fontSize',...]"
- //
- // It is possible to override the default dropdown list by providing an Array for the `custom` property when
- // instantiating this plugin, e.g.
- //
- // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', custom:['Verdana','Myriad','Garamond']},...]"
- //
- // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with
- // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families)
- //
- // Note that the editor is often unable to properly handle font styling information defined outside
- // the context of the current editor instance, such as pre-populated HTML.
- // useDefaultCommand: [protected] Boolean
- // Override _Plugin.useDefaultCommand...
- // processing is handled by this plugin, not by dijit.Editor.
- useDefaultCommand: false,
- _initButton: function(){
- // summary:
- // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar,
- // rather than a simple button.
- // tags:
- // protected
- // Create the widget to go into the toolbar (the so-called "button")
- var clazz = {
- fontName: _FontNameDropDown,
- fontSize: _FontSizeDropDown,
- formatBlock: _FormatBlockDropDown
- }[this.command],
- params = this.params;
- // For back-compat reasons support setting custom values via "custom" parameter
- // rather than "values" parameter
- if(this.params.custom){
- params.values = this.params.custom;
- }
- var editor = this.editor;
- this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params));
- // Reflect changes to the drop down in the editor
- this.connect(this.button.select, "onChange", function(choice){
- // User invoked change, since all internal updates set priorityChange to false and will
- // not trigger an onChange event.
- if(this.editor.focused){
- // put focus back in the iframe, unless focus has somehow been shifted out of the editor completely
- this.editor.focus();
- }
- if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; }
- // Invoke, the editor already normalizes commands called through its
- // execCommand.
- if(this.button._execCommand){
- this.button._execCommand(this.editor, this.command, choice);
- }else{
- this.editor.execCommand(this.command, choice);
- }
- });
- },
- updateState: function(){
- // summary:
- // Overrides _Plugin.updateState(). This controls updating the menu
- // options to the right values on state changes in the document (that trigger a
- // test of the actions.)
- // It set value of drop down in toolbar to reflect font/font size/format block
- // of text at current caret position.
- // tags:
- // protected
- var _e = this.editor;
- var _c = this.command;
- if(!_e || !_e.isLoaded || !_c.length){ return; }
- if(this.button){
- var disabled = this.get("disabled");
- this.button.set("disabled", disabled);
- if(disabled){ return; }
- var value;
- try{
- value = _e.queryCommandValue(_c) || "";
- }catch(e){
- //Firefox may throw error above if the editor is just loaded, ignore it
- value = "";
- }
- // strip off single quotes, if any
- var quoted = lang.isString(value) && (value.match(/'([^']*)'/) || value.match(/"([^"]*)"/));
- if(quoted){ value = quoted[1]; }
- if(_c === "formatBlock"){
- if(!value || value == "p"){
- // Some browsers (WebKit) doesn't actually get the tag info right.
- // and IE returns paragraph when in a DIV!, so incorrect a lot,
- // so we have double-check it.
- value = null;
- var elem;
- // Try to find the current element where the caret is.
- var sel = rangeapi.getSelection(this.editor.window);
- if(sel && sel.rangeCount > 0){
- var range = sel.getRangeAt(0);
- if(range){
- elem = range.endContainer;
- }
- }
- // Okay, now see if we can find one of the formatting types we're in.
- while(elem && elem !== _e.editNode && elem !== _e.document){
- var tg = elem.tagName?elem.tagName.toLowerCase():"";
- if(tg && array.indexOf(this.button.values, tg) > -1){
- value = tg;
- break;
- }
- elem = elem.parentNode;
- }
- if(!value){
- // Still no value, so lets select 'none'.
- value = "noFormat";
- }
- }else{
- // Check that the block format is one allowed, if not,
- // null it so that it gets set to empty.
- if(array.indexOf(this.button.values, value) < 0){
- value = "noFormat";
- }
- }
- }
- if(value !== this.button.get("value")){
- // Set the value, but denote it is not a priority change, so no
- // onchange fires.
- this.button.set('value', value, false);
- }
- }
- }
- });
- // Register these plugins
- array.forEach(["fontName", "fontSize", "formatBlock"], function(name){
- _Plugin.registry[name] = function(args){
- return new FontChoice({
- command: name,
- plainText: args.plainText
- });
- };
- });
- });
- },
- 'dijit/_editor/html':function(){
- define("dijit/_editor/html", [
- "dojo/_base/array",
- "dojo/_base/lang", // lang.getObject
- "dojo/_base/sniff", // has("ie")
- ".." // for exporting symbols to dijit._editor (remove for 2.0)
- ], function(array, lang, has, dijit){
- // module:
- // dijit/_editor/html
- // summary:
- // Utility functions used by editor
- // Tests for DOMNode.attributes[] behavior:
- // - dom-attributes-explicit - attributes[] only lists explicitly user specified attributes
- // - dom-attributes-specified-flag (IE8) - need to check attr.specified flag to skip attributes user didn't specify
- // - Otherwise, in IE6-7. attributes[] will list hundreds of values, so need to do outerHTML to get attrs instead.
- var form = document.createElement("form");
- has.add("dom-attributes-explicit", form.attributes.length == 0); // W3C
- has.add("dom-attributes-specified-flag", form.attributes.length > 0 && form.attributes.length < 40); // IE8
- lang.getObject("_editor", true, dijit);
- dijit._editor.escapeXml=function(/*String*/str, /*Boolean?*/noSingleQuotes){
- // summary:
- // Adds escape sequences for special characters in XML: &<>"'
- // Optionally skips escapes for single quotes
- str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """);
- if(!noSingleQuotes){
- str = str.replace(/'/gm, "'");
- }
- return str; // string
- };
- dijit._editor.getNodeHtml=function(/* DomNode */node){
- var output;
- switch(node.nodeType){
- case 1: //element node
- var lName = node.nodeName.toLowerCase();
- if(!lName || lName.charAt(0) == "/"){
- // IE does some strange things with malformed HTML input, like
- // treating a close tag </span> without an open tag <span>, as
- // a new tag with tagName of /span. Corrupts output HTML, remove
- // them. Other browsers don't prefix tags that way, so will
- // never show up.
- return "";
- }
- output = '<' + lName;
- //store the list of attributes and sort it to have the
- //attributes appear in the dictionary order
- var attrarray = [], attrhash = {};
- var attr;
- if(has("dom-attributes-explicit") || has("dom-attributes-specified-flag")){
- // IE8+ and all other browsers.
- var i = 0;
- while((attr = node.attributes[i++])){
- // ignore all attributes starting with _dj which are
- // internal temporary attributes used by the editor
- var n = attr.name;
- if(n.substr(0,3) !== '_dj' &&
- (!has("dom-attributes-specified-flag") || attr.specified) &&
- !(n in attrhash)){ // workaround repeated attributes bug in IE8 (LinkDialog test)
- var v = attr.value;
- if(n == 'src' || n == 'href'){
- if(node.getAttribute('_djrealurl')){
- v = node.getAttribute('_djrealurl');
- }
- }
- if(has("ie") === 8 && n === "style"){
- v = v.replace("HEIGHT:", "height:").replace("WIDTH:", "width:");
- }
- attrarray.push([n,v]);
- attrhash[n] = v;
- }
- }
- }else{
- // IE6-7 code path
- var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false);
- var s = clone.outerHTML;
- // Split up and manage the attrs via regexp
- // similar to prettyPrint attr logic.
- var rgxp_attrsMatch = /[\w-]+=("[^"]*"|'[^']*'|\S*)/gi
- var attrSplit = s.match(rgxp_attrsMatch);
- s = s.substr(0, s.indexOf('>'));
- array.forEach(attrSplit, function(attr){
- if(attr){
- var idx = attr.indexOf("=");
- if(idx > 0){
- var key = attr.substring(0,idx);
- if(key.substr(0,3) != '_dj'){
- if(key == 'src' || key == 'href'){
- if(node.getAttribute('_djrealurl')){
- attrarray.push([key,node.getAttribute('_djrealurl')]);
- return;
- }
- }
- var val, match;
- switch(key){
- case 'style':
- val = node.style.cssText.toLowerCase();
- break;
- case 'class':
- val = node.className;
- break;
- case 'width':
- if(lName === "img"){
- // This somehow gets lost on IE for IMG tags and the like
- // and we have to find it in outerHTML, known IE oddity.
- match=/width=(\S+)/i.exec(s);
- if(match){
- val = match[1];
- }
- break;
- }
- case 'height':
- if(lName === "img"){
- // This somehow gets lost on IE for IMG tags and the like
- // and we have to find it in outerHTML, known IE oddity.
- match=/height=(\S+)/i.exec(s);
- if(match){
- val = match[1];
- }
- break;
- }
- default:
- val = node.getAttribute(key);
- }
- if(val != null){
- attrarray.push([key, val.toString()]);
- }
- }
- }
- }
- }, this);
- }
- attrarray.sort(function(a,b){
- return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1);
- });
- var j = 0;
- while((attr = attrarray[j++])){
- output += ' ' + attr[0] + '="' +
- (lang.isString(attr[1]) ? dijit._editor.escapeXml(attr[1], true) : attr[1]) + '"';
- }
- if(lName === "script"){
- // Browsers handle script tags differently in how you get content,
- // but innerHTML always seems to work, so insert its content that way
- // Yes, it's bad to allow script tags in the editor code, but some people
- // seem to want to do it, so we need to at least return them right.
- // other plugins/filters can strip them.
- output += '>' + node.innerHTML +'</' + lName + '>';
- }else{
- if(node.childNodes.length){
- output += '>' + dijit._editor.getChildrenHtml(node)+'</' + lName +'>';
- }else{
- switch(lName){
- case 'br':
- case 'hr':
- case 'img':
- case 'input':
- case 'base':
- case 'meta':
- case 'area':
- case 'basefont':
- // These should all be singly closed
- output += ' />';
- break;
- default:
- // Assume XML style separate closure for everything else.
- output += '></' + lName + '>';
- }
- }
- }
- break;
- case 4: // cdata
- case 3: // text
- // FIXME:
- output = dijit._editor.escapeXml(node.nodeValue, true);
- break;
- case 8: //comment
- // FIXME:
- output = '<!--' + dijit._editor.escapeXml(node.nodeValue, true) + '-->';
- break;
- default:
- output = "<!-- Element not recognized - Type: " + node.nodeType + " Name: " + node.nodeName + "-->";
- }
- return output;
- };
- dijit._editor.getChildrenHtml = function(/* DomNode */dom){
- // summary:
- // Returns the html content of a DomNode and children
- var out = "";
- if(!dom){ return out; }
- var nodes = dom["childNodes"] || dom;
- //IE issue.
- //If we have an actual node we can check parent relationships on for IE,
- //We should check, as IE sometimes builds invalid DOMS. If no parent, we can't check
- //And should just process it and hope for the best.
- var checkParent = !has("ie") || nodes !== dom;
- var node, i = 0;
- while((node = nodes[i++])){
- //IE is broken. DOMs are supposed to be a tree. But in the case of malformed HTML, IE generates a graph
- //meaning one node ends up with multiple references (multiple parents). This is totally wrong and invalid, but
- //such is what it is. We have to keep track and check for this because otherise the source output HTML will have dups.
- //No other browser generates a graph. Leave it to IE to break a fundamental DOM rule. So, we check the parent if we can
- //If we can't, nothing more we can do other than walk it.
- if(!checkParent || node.parentNode == dom){
- out += dijit._editor.getNodeHtml(node);
- }
- }
- return out; // String
- };
- return dijit._editor;
- });
- },
- 'dijit/_editor/BuxRichText':function(){
- define("dijit/_editor/BuxRichText", [
- "dojo/_base/array", // array.forEach array.indexOf array.some
- "dojo/_base/config", // config
- "dojo/_base/declare", // declare
- "dojo/_base/Deferred", // Deferred
- "dojo/dom", // dom.byId
- "dojo/dom-attr", // domAttr.set or get
- "dojo/dom-class", // domClass.add domClass.remove
- "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place
- "dojo/dom-geometry", // domGeometry.position
- "dojo/dom-style", // domStyle.getComputedStyle domStyle.set
- "dojo/_base/event", // event.stop
- "dojo/_base/kernel", // kernel.deprecated
- "dojo/keys", // keys.BACKSPACE keys.TAB
- "dojo/_base/lang", // lang.clone lang.hitch lang.isArray lang.isFunction lang.isString lang.trim
- "dojo/on", // on()
- "dojo/query", // query
- "dojo/ready", // ready
- "dojo/_base/sniff", // has("ie") has("mozilla") has("opera") has("safari") has("webkit")
- "dojo/topic", // topic.publish() (publish)
- "dojo/_base/unload", // unload
- "dojo/_base/url", // url
- "dojo/_base/window", // win.body win.doc.body.focus win.doc.createElement win.global.location win.withGlobal
- "../_Widget",
- "../_CssStateMixin",
- "./selection",
- "./range",
- "./html",
- "../focus",
- ".." // dijit._scopeName
- ], function(array, config, declare, Deferred, dom, domAttr, domClass, domConstruct, domGeometry, domStyle,
- event, kernel, keys, lang, on, query, ready, has, topic, unload, _Url, win,
- _Widget, _CssStateMixin, selectionapi, rangeapi, htmlapi, focus, dijit){
- /*=====
- var _Widget = dijit._Widget;
- var _CssStateMixin = dijit._CssStateMixin;
- =====*/
- // module:
- // dijit/_editor/BuxRichText
- // summary:
- // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic
- // WYSIWYG editing features.
- // if you want to allow for rich text saving with back/forward actions, you must add a text area to your page with
- // the id==dijit._scopeName + "._editor.BuxRichText.value" (typically "dijit._editor.BuxRichText.value). For example,
- // something like this will work:
- //
- // <textarea id="dijit._editor.BuxRichText.value" style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>
- //
- var BuxRichText = declare("dijit._editor.BuxRichText", [_Widget, _CssStateMixin], {
- // summary:
- // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic
- // WYSIWYG editing features.
- //
- // description:
- // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic
- // WYSIWYG editing features. It also encapsulates the differences
- // of different js engines for various browsers. Do not use this widget
- // with an HTML <TEXTAREA> tag, since the browser unescapes XML escape characters,
- // like <. This can have unexpected behavior and lead to security issues
- // such as scripting attacks.
- //
- // tags:
- // private
- constructor: function(params){
- // contentPreFilters: Function(String)[]
- // Pre content filter function register array.
- // these filters will be executed before the actual
- // editing area gets the html content.
- this.contentPreFilters = [];
- // contentPostFilters: Function(String)[]
- // post content filter function register array.
- // These will be used on the resulting html
- // from contentDomPostFilters. The resulting
- // content is the final html (returned by getValue()).
- this.contentPostFilters = [];
- // contentDomPreFilters: Function(DomNode)[]
- // Pre content dom filter function register array.
- // These filters are applied after the result from
- // contentPreFilters are set to the editing area.
- this.contentDomPreFilters = [];
- // contentDomPostFilters: Function(DomNode)[]
- // Post content dom filter function register array.
- // These filters are executed on the editing area dom.
- // The result from these will be passed to contentPostFilters.
- this.contentDomPostFilters = [];
- // editingAreaStyleSheets: dojo._URL[]
- // array to store all the stylesheets applied to the editing area
- this.editingAreaStyleSheets = [];
- // Make a copy of this.events before we start writing into it, otherwise we
- // will modify the prototype which leads to bad things on pages w/multiple editors
- this.events = [].concat(this.events);
- this._keyHandlers = {};
- if(params && lang.isString(params.value)){
- this.value = params.value;
- }
- this.onLoadDeferred = new Deferred();
- },
- baseClass: "dijitEditor",
- // inheritWidth: Boolean
- // whether to inherit the parent's width or simply use 100%
- inheritWidth: false,
- // focusOnLoad: [deprecated] Boolean
- // Focus into this widget when the page is loaded
- focusOnLoad: false,
- // name: String?
- // Specifies the name of a (hidden) <textarea> node on the page that's used to save
- // the editor content on page leave. Used to restore editor contents after navigating
- // to a new page and then hitting the back button.
- name: "",
- // styleSheets: [const] String
- // semicolon (";") separated list of css files for the editing area
- styleSheets: "",
- // height: String
- // Set height to fix the editor at a specific height, with scrolling.
- // By default, this is 300px. If you want to have the editor always
- // resizes to accommodate the content, use AlwaysShowToolbar plugin
- // and set height="". If this editor is used within a layout widget,
- // set height="100%".
- height: "300px",
- // minHeight: String
- // The minimum height that the editor should have.
- minHeight: "1em",
- // isClosed: [private] Boolean
- isClosed: true,
- // isLoaded: [private] Boolean
- isLoaded: false,
- // _SEPARATOR: [private] String
- // Used to concat contents from multiple editors into a single string,
- // so they can be saved into a single <textarea> node. See "name" attribute.
- _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
- // _NAME_CONTENT_SEP: [private] String
- // USed to separate name from content. Just a colon isn't safe.
- _NAME_CONTENT_SEP: "@@**%%:%%**@@",
- // onLoadDeferred: [readonly] dojo.Deferred
- // Deferred which is fired when the editor finishes loading.
- // Call myEditor.onLoadDeferred.then(callback) it to be informed
- // when the rich-text area initialization is finalized.
- onLoadDeferred: null,
- // isTabIndent: Boolean
- // Make tab key and shift-tab indent and outdent rather than navigating.
- // Caution: sing this makes web pages inaccessible to users unable to use a mouse.
- isTabIndent: false,
- // disableSpellCheck: [const] Boolean
- // When true, disables the browser's native spell checking, if supported.
- // Works only in Firefox.
- disableSpellCheck: false,
- postCreate: function(){
- if("textarea" === this.domNode.tagName.toLowerCase()){
- console.warn("BuxRichText should not be used with the TEXTAREA tag. See dijit._editor.BuxRichText docs.");
- }
- // Push in the builtin filters now, making them the first executed, but not over-riding anything
- // users passed in. See: #6062
- this.contentPreFilters = [lang.hitch(this, "_preFixUrlAttributes")].concat(this.contentPreFilters);
- if(has("mozilla")){
- this.contentPreFilters = [this._normalizeFontStyle].concat(this.contentPreFilters);
- this.contentPostFilters = [this._removeMozBogus].concat(this.contentPostFilters);
- }
- if(has("webkit")){
- // Try to clean up WebKit bogus artifacts. The inserted classes
- // made by WebKit sometimes messes things up.
- this.contentPreFilters = [this._removeWebkitBogus].concat(this.contentPreFilters);
- this.contentPostFilters = [this._removeWebkitBogus].concat(this.contentPostFilters);
- }
- if(has("ie") || has("trident")){
- // IE generates <strong> and <em> but we want to normalize to <b> and <i>
- // Still happens in IE11!
- this.contentPostFilters = [this._normalizeFontStyle].concat(this.contentPostFilters);
- this.contentDomPostFilters = [lang.hitch(this, this._stripBreakerNodes)].concat(this.contentDomPostFilters);
- }
- this.inherited(arguments);
- topic.publish(dijit._scopeName + "._editor.BuxRichText::init", this);
- this.open();
- this.setupDefaultShortcuts();
- },
- setupDefaultShortcuts: function(){
- // summary:
- // Add some default key handlers
- // description:
- // Overwrite this to setup your own handlers. The default
- // implementation does not use Editor commands, but directly
- // executes the builtin commands within the underlying browser
- // support.
- // tags:
- // protected
- var exec = lang.hitch(this, function(cmd, arg){
- return function(){
- return !this.execCommand(cmd,arg);
- };
- });
- var ctrlKeyHandlers = {
- b: exec("bold"),
- i: exec("italic"),
- u: exec("underline"),
- a: exec("selectall"),
- s: function(){ this.save(true); },
- m: function(){ this.isTabIndent = !this.isTabIndent; },
- "1": exec("formatblock", "h1"),
- "2": exec("formatblock", "h2"),
- "3": exec("formatblock", "h3"),
- "4": exec("formatblock", "h4"),
- "\\": exec("insertunorderedlist")
- };
- if(!has("ie")){
- ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo?
- }
- var key;
- for(key in ctrlKeyHandlers){
- this.addKeyHandler(key, true, false, ctrlKeyHandlers[key]);
- }
- },
- // events: [private] String[]
- // events which should be connected to the underlying editing area
- events: ["onKeyPress", "onKeyDown", "onKeyUp"], // onClick handled specially
- // captureEvents: [deprecated] String[]
- // Events which should be connected to the underlying editing
- // area, events in this array will be addListener with
- // capture=true.
- // TODO: looking at the code I don't see any distinction between events and captureEvents,
- // so get rid of this for 2.0 if not sooner
- captureEvents: [],
- _editorCommandsLocalized: false,
- _localizeEditorCommands: function(){
- // summary:
- // When IE is running in a non-English locale, the API actually changes,
- // so that we have to say (for example) danraku instead of p (for paragraph).
- // Handle that here.
- // tags:
- // private
- if(BuxRichText._editorCommandsLocalized){
- // Use the already generate cache of mappings.
- this._local2NativeFormatNames = BuxRichText._local2NativeFormatNames;
- this._native2LocalFormatNames = BuxRichText._native2LocalFormatNames;
- return;
- }
- BuxRichText._editorCommandsLocalized = true;
- BuxRichText._local2NativeFormatNames = {};
- BuxRichText._native2LocalFormatNames = {};
- this._local2NativeFormatNames = BuxRichText._local2NativeFormatNames;
- this._native2LocalFormatNames = BuxRichText._native2LocalFormatNames;
- //in IE, names for blockformat is locale dependent, so we cache the values here
- //put p after div, so if IE returns Normal, we show it as paragraph
- //We can distinguish p and div if IE returns Normal, however, in order to detect that,
- //we have to call this.document.selection.createRange().parentElement() or such, which
- //could slow things down. Leave it as it is for now
- var formats = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'address'];
- var localhtml = "", format, i=0;
- while((format=formats[i++])){
- //append a <br> after each element to separate the elements more reliably
- if(format.charAt(1) !== 'l'){
- localhtml += "<"+format+"><span>content</span></"+format+"><br/>";
- }else{
- localhtml += "<"+format+"><li>content</li></"+format+"><br/>";
- }
- }
- // queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
- // Also, IE9 does weird stuff unless we do it inside the editor iframe.
- var style = { position: "absolute", top: "0px", zIndex: 10, opacity: 0.01 };
- var div = domConstruct.create('div', {style: style, innerHTML: localhtml});
- win.body().appendChild(div);
- // IE9 has a timing issue with doing this right after setting
- // the inner HTML, so put a delay in.
- var inject = lang.hitch(this, function(){
- var node = div.firstChild;
- while(node){
- try{
- selectionapi.selectElement(node.firstChild);
- var nativename = node.tagName.toLowerCase();
- this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock");
- this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
- node = node.nextSibling.nextSibling;
- //console.log("Mapped: ", nativename, " to: ", this._local2NativeFormatNames[nativename]);
- }catch(e){ /*Sqelch the occasional IE9 error */ }
- }
- div.parentNode.removeChild(div);
- div.innerHTML = "";
- });
- setTimeout(inject, 0);
- },
- open: function(/*DomNode?*/ element){
- // summary:
- // Transforms the node referenced in this.domNode into a rich text editing
- // node.
- // description:
- // Sets up the editing area asynchronously. This will result in
- // the creation and replacement with an iframe.
- // tags:
- // private
- if(!this.onLoadDeferred || this.onLoadDeferred.fired >= 0){
- this.onLoadDeferred = new Deferred();
- }
- if(!this.isClosed){ this.close(); }
- topic.publish(dijit._scopeName + "._editor.BuxRichText::open", this);
- if(arguments.length === 1 && element.nodeName){ // else unchanged
- this.domNode = element;
- }
- var dn = this.domNode;
- // "html" will hold the innerHTML of the srcNodeRef and will be used to
- // initialize the editor.
- var html;
- if(lang.isString(this.value)){
- // Allow setting the editor content programmatically instead of
- // relying on the initial content being contained within the target
- // domNode.
- html = this.value;
- delete this.value;
- dn.innerHTML = "";
- }else if(dn.nodeName && dn.nodeName.toLowerCase() == "textarea"){
- // if we were created from a textarea, then we need to create a
- // new editing harness node.
- var ta = (this.textarea = dn);
- this.name = ta.name;
- html = ta.value;
- dn = this.domNode = win.doc.createElement("div");
- dn.setAttribute('widgetId', this.id);
- ta.removeAttribute('widgetId');
- dn.cssText = ta.cssText;
- dn.className += " " + ta.className;
- domConstruct.place(dn, ta, "before");
- var tmpFunc = lang.hitch(this, function(){
- //some browsers refuse to submit display=none textarea, so
- //move the textarea off screen instead
- domStyle.set(ta, {
- display: "block",
- position: "absolute",
- top: "-1000px"
- });
- if(has("ie")){ //nasty IE bug: abnormal formatting if overflow is not hidden
- var s = ta.style;
- this.__overflow = s.overflow;
- s.overflow = "hidden";
- }
- });
- if(has("ie")){
- setTimeout(tmpFunc, 10);
- }else{
- tmpFunc();
- }
- if(ta.form){
- var resetValue = ta.value;
- this.reset = function(){
- var current = this.getValue();
- if(current !== resetValue){
- this.replaceValue(resetValue);
- }
- };
- on(ta.form, "submit", lang.hitch(this, function(){
- // Copy value to the <textarea> so it gets submitted along with form.
- // FIXME: should we be calling close() here instead?
- domAttr.set(ta, 'disabled', this.disabled); // don't submit the value if disabled
- ta.value = this.getValue();
- }));
- }
- }else{
- html = htmlapi.getChildrenHtml(dn);
- dn.innerHTML = "";
- }
- this.value = html;
- // If we're a list item we have to put in a blank line to force the
- // bullet to nicely align at the top of text
- if(dn.nodeName && dn.nodeName === "LI"){
- dn.innerHTML = " <br>";
- }
- // Construct the editor div structure.
- this.header = dn.ownerDocument.createElement("div");
- dn.appendChild(this.header);
- this.editingArea = dn.ownerDocument.createElement("div");
- dn.appendChild(this.editingArea);
- this.footer = dn.ownerDocument.createElement("div");
- dn.appendChild(this.footer);
- if(!this.name){
- this.name = this.id + "_AUTOGEN";
- }
- // User has pressed back/forward button so we lost the text in the editor, but it's saved
- // in a hidden <textarea> (which contains the data for all the editors on this page),
- // so get editor value from there
- if(this.name !== "" && (!config["useXDomain"] || config["allowXdRichTextSave"])){
- var saveTextarea = dom.byId(dijit._scopeName + "._editor.BuxRichText.value");
- if(saveTextarea && saveTextarea.value !== ""){
- var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;
- while((dat=datas[i++])){
- var data = dat.split(this._NAME_CONTENT_SEP);
- if(data[0] === this.name){
- html = data[1];
- datas = datas.splice(i, 1);
- saveTextarea.value = datas.join(this._SEPARATOR);
- break;
- }
- }
- }
- if(!BuxRichText._globalSaveHandler){
- BuxRichText._globalSaveHandler = {};
- unload.addOnUnload(function(){
- var id;
- for(id in BuxRichText._globalSaveHandler){
- var f = BuxRichText._globalSaveHandler[id];
- if(lang.isFunction(f)){
- f();
- }
- }
- });
- }
- BuxRichText._globalSaveHandler[this.id] = lang.hitch(this, "_saveContent");
- }
- this.isClosed = false;
- var ifr = (this.editorObject = this.iframe = win.doc.createElement('iframe'));
- ifr.id = this.id+"_iframe";
- ifr.style.border = "none";
- ifr.style.width = "100%";
- if(this._layoutMode){
- // iframe should be 100% height, thus getting it's height from surrounding
- // <div> (which has the correct height set by Editor)
- ifr.style.height = "100%";
- }else{
- if(has("ie") >= 7){
- if(this.height){
- ifr.style.height = this.height;
- }
- if(this.minHeight){
- ifr.style.minHeight = this.minHeight;
- }
- }else{
- ifr.style.height = this.height ? this.height : this.minHeight;
- }
- }
- ifr.frameBorder = 0;
- ifr._loadFunc = lang.hitch( this, function(w){
- this.window = w;
- this.document = w.document;
- if(has("ie")){
- this._localizeEditorCommands();
- }
- // Do final setup and set initial contents of editor
- this.onLoad(html);
- });
- // Attach iframe to document, and set the initial (blank) content.
- var src = this._getIframeDocTxt().replace(/\\/g, "\\\\").replace(/'/g, "\\'"),
- s;
- // IE10 and earlier will throw an "Access is denied" error when attempting to access the parent frame if
- // document.domain has been set, unless the child frame also has the same document.domain set. The child frame
- // can only set document.domain while the document is being constructed using open/write/close; attempting to
- // set it later results in a different "This method can't be used in this context" error. See #17529
- if (has("ie") < 11) {
- s = 'javascript:document.open();try{parent.window;}catch(e){document.domain="' + document.domain + '";}' +
- 'document.write(\'' + src + '\');document.close()';
- }
- else {
- s = "javascript: '" + src + "'";
- }
- if(has("ie") == 9){
- // On IE9, attach to document before setting the content, to avoid problem w/iframe running in
- // wrong security context, see #16633.
- this.editingArea.appendChild(ifr);
- ifr.src = s;
- }else{
- // For other browsers, set src first, especially for IE6/7 where attaching first gives a warning on
- // https:// about "this page contains secure and insecure items, do you want to view both?"
- ifr.setAttribute('src', s);
- this.editingArea.appendChild(ifr);
- }
- if(has("safari") <= 4){
- src = ifr.getAttribute("src");
- if(!src || src.indexOf("javascript") === -1){
- // Safari 4 and earlier sometimes act oddly
- // So we have to set it again.
- setTimeout(function(){ifr.setAttribute('src', s);},0);
- }
- }
- // TODO: this is a guess at the default line-height, kinda works
- if(dn.nodeName === "LI"){
- dn.lastChild.style.marginTop = "-1.2em";
- }
- domClass.add(this.domNode, this.baseClass);
- },
- //static cache variables shared among all instance of this class
- _local2NativeFormatNames: {},
- _native2LocalFormatNames: {},
- _getIframeDocTxt: function(){
- // summary:
- // Generates the boilerplate text of the document inside the iframe (ie, <html><head>...</head><body/></html>).
- // Editor content (if not blank) should be added afterwards.
- // tags:
- // private
- var _cs = domStyle.getComputedStyle(this.domNode);
- // The contents inside of <body>. The real contents are set later via a call to setValue().
- // In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly
- // expand/contract the editor as the content changes.
- var html = "<div id='dijitEditorBody'></div>";
- var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" ");
- // line height is tricky - applying a units value will mess things up.
- // if we can't get a non-units value, bail out.
- var lineHeight = _cs.lineHeight;
- if(lineHeight.indexOf("px") >= 0){
- lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize);
- // console.debug(lineHeight);
- }else if(lineHeight.indexOf("em")>=0){
- lineHeight = parseFloat(lineHeight);
- }else{
- // If we can't get a non-units value, just default
- // it to the CSS spec default of 'normal'. Seems to
- // work better, esp on IE, than '1.0'
- lineHeight = "normal";
- }
- var userStyle = "";
- var self = this;
- this.style.replace(/(^|;)\s*(line-|font-?)[^;]+/ig, function(match){
- match = match.replace(/^;/ig,"") + ';';
- var s = match.split(":")[0];
- if(s){
- s = lang.trim(s);
- s = s.toLowerCase();
- var i;
- var sC = "";
- for(i = 0; i < s.length; i++){
- var c = s.charAt(i);
- switch(c){
- case "-":
- i++;
- c = s.charAt(i).toUpperCase();
- default:
- sC += c;
- }
- }
- domStyle.set(self.domNode, sC, "");
- }
- userStyle += match + ';';
- });
- // need to find any associated label element and update iframe document title
- var label=query('label[for="'+this.id+'"]');
- return [
- this.isLeftToRight() ? "<html>\n<head>\n" : "<html dir='rtl'>\n<head>\n",
- (has("mozilla") && label.length ? "<title>" + label[0].innerHTML + "</title>\n" : ""),
- "<meta http-equiv='Content-Type' content='text/html'>\n",
- "<style>\n",
- "\tbody,html {\n",
- "\t\tbackground:transparent;\n",
- "\t\tpadding: 1px 0 0 0;\n",
- "\t\tmargin: -1px 0 0 0;\n", // remove extraneous vertical scrollbar on safari and firefox
- "\t}\n",
- "\tbody,html,#dijitEditorBody { outline: none; }",
- // Set <body> to expand to full size of editor, so clicking anywhere will work.
- // Except in auto-expand mode, in which case the editor expands to the size of <body>.
- // Also determine how scrollers should be applied. In autoexpand mode (height = "") no scrollers on y at all.
- // But in fixed height mode we want both x/y scrollers.
- // Scrollers go on <body> since it's been set to height: 100%.
- "html { height: 100%; width: 100%; overflow: hidden; }\n", // scroll bar is on #dijitEditorBody, shouldn't be on <html>
- this.height ? "\tbody,#dijitEditorBody { height: 100%; width: 100%; overflow: auto; }\n" :
- "\tbody,#dijitEditorBody { min-height: " + this.minHeight + "; width: 100%; overflow-x: auto; overflow-y: hidden; }\n",
- // TODO: left positioning will cause contents to disappear out of view
- // if it gets too wide for the visible area
- "\tbody{\n",
- "\t\ttop:0px;\n",
- "\t\tleft:0px;\n",
- "\t\tright:0px;\n",
- "\t\tfont:", font, ";\n",
- ((this.height||has("opera")) ? "" : "\t\tposition: fixed;\n"),
- "\t\tline-height:", lineHeight,";\n",
- "\t}\n",
- "\tp{ margin: 1em 0; }\n",
- "\tli > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; }\n",
- // Can't set min-height in IE>=9, it puts layout on li, which puts move/resize handles.
- (has("ie") || has("trident") ? "" : "\tli{ min-height:1.2em; }\n"),
- "</style>\n",
- this._applyEditingAreaStyleSheets(),"\n",
- "</head>\n<body ",
- "</head>\n<body role='main' ",
- // Onload handler fills in real editor content.
- // On IE9, sometimes onload is called twice, and the first time frameElement is null (test_FullScreen.html)
- "onload='frameElement && frameElement._loadFunc(window,document)' ",
- "style='"+userStyle+"'>", html, "</body>\n</html>"
- ].join(""); // String
- },
- _applyEditingAreaStyleSheets: function(){
- // summary:
- // apply the specified css files in styleSheets
- // tags:
- // private
- var files = [];
- if(this.styleSheets){
- files = this.styleSheets.split(';');
- this.styleSheets = '';
- }
- //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet
- files = files.concat(this.editingAreaStyleSheets);
- this.editingAreaStyleSheets = [];
- var text='', i=0, url;
- while((url=files[i++])){
- var abstring = (new _Url(win.global.location, url)).toString();
- this.editingAreaStyleSheets.push(abstring);
- text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>';
- }
- return text;
- },
- addStyleSheet: function(/*dojo._Url*/ uri){
- // summary:
- // add an external stylesheet for the editing area
- // uri:
- // A dojo.uri.Uri pointing to the url of the external css file
- var url=uri.toString();
- //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
- if(url.charAt(0) === '.' || (url.charAt(0) !== '/' && !uri.host)){
- url = (new _Url(win.global.location, url)).toString();
- }
- if(array.indexOf(this.editingAreaStyleSheets, url) > -1){
- // console.debug("dijit._editor.BuxRichText.addStyleSheet: Style sheet "+url+" is already applied");
- return;
- }
- this.editingAreaStyleSheets.push(url);
- this.onLoadDeferred.addCallback(lang.hitch(this, function(){
- if(this.document.createStyleSheet){ //IE
- this.document.createStyleSheet(url);
- }else{ //other browser
- var head = this.document.getElementsByTagName("head")[0];
- var stylesheet = this.document.createElement("link");
- stylesheet.rel="stylesheet";
- stylesheet.type="text/css";
- stylesheet.href=url;
- head.appendChild(stylesheet);
- }
- }));
- },
- removeStyleSheet: function(/*dojo._Url*/ uri){
- // summary:
- // remove an external stylesheet for the editing area
- var url=uri.toString();
- //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
- if(url.charAt(0) === '.' || (url.charAt(0) !== '/' && !uri.host)){
- url = (new _Url(win.global.location, url)).toString();
- }
- var index = array.indexOf(this.editingAreaStyleSheets, url);
- if(index === -1){
- // console.debug("dijit._editor.BuxRichText.removeStyleSheet: Style sheet "+url+" has not been applied");
- return;
- }
- delete this.editingAreaStyleSheets[index];
- win.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan();
- },
- // disabled: Boolean
- // The editor is disabled; the text cannot be changed.
- disabled: false,
- _mozSettingProps: {'styleWithCSS':false},
- _setDisabledAttr: function(/*Boolean*/ value){
- value = !!value;
- this._set("disabled", value);
- if(!this.isLoaded){
- return;
- } // this method requires init to be complete
- var preventIEfocus = has("ie") && (this.isLoaded || !this.focusOnLoad);
- if(preventIEfocus){
- this.editNode.unselectable = "on";
- }
- this.editNode.contentEditable = !value;
- this.editNode.tabIndex = value ? "-1" : this.tabIndex;
- if(preventIEfocus){
- this.defer(function(){
- if(this.editNode){ // guard in case widget destroyed before timeout
- this.editNode.unselectable = "off";
- }
- });
- }
- if(has("mozilla") && !value && this._mozSettingProps){
- var ps = this._mozSettingProps;
- var n;
- for(n in ps){
- if(ps.hasOwnProperty(n)){
- try{
- this.document.execCommand(n, false, ps[n]);
- }catch(e2){
- }
- }
- }
- }
- this._disabledOK = true;
- },
- /* Event handlers
- *****************/
- onLoad: function(/*String*/ html){
- // summary:
- // Handler after the iframe finishes loading.
- // html: String
- // Editor contents should be set to this value
- // tags:
- // protected
- // TODO: rename this to _onLoad, make empty public onLoad() method, deprecate/make protected onLoadDeferred handler?
- if(!this.window.__registeredWindow){
- this.window.__registeredWindow = true;
- this._iframeRegHandle = focus.registerIframe(this.iframe);
- }
- // there's a wrapper div around the content, see _getIframeDocTxt().
- this.editNode = this.document.body.firstChild;
- var _this = this;
- // Helper code so IE and FF skip over focusing on the <iframe> and just focus on the inner <div>.
- // See #4996 IE wants to focus the BODY tag.
- this.beforeIframeNode = domConstruct.place("<div tabIndex=-1></div>", this.iframe, "before");
- this.afterIframeNode = domConstruct.place("<div tabIndex=-1></div>", this.iframe, "after");
- this.iframe.onfocus = this.document.onfocus = function(){
- _this.editNode.focus();
- };
- this.focusNode = this.editNode; // for InlineEditBox
- var events = this.events.concat(this.captureEvents);
- var ap = this.iframe ? this.document : this.editNode;
- array.forEach(events, function(item){
- this.connect(ap, item.toLowerCase(), item);
- }, this);
- this.connect(ap, "onmouseup", "onClick"); // mouseup in the margin does not generate an onclick event
- if(has("ie")){ // IE contentEditable
- this.connect(this.document, "onmousedown", "_onIEMouseDown"); // #4996 fix focus
- // give the node Layout on IE
- // TODO: this may no longer be needed, since we've reverted IE to using an iframe,
- // not contentEditable. Removing it would also probably remove the need for creating
- // the extra <div> in _getIframeDocTxt()
- this.editNode.style.zoom = 1.0;
- }else{
- this.connect(this.document, "onmousedown", function(){
- // Clear the moveToStart focus, as mouse
- // down will set cursor point. Required to properly
- // work with selection/position driven plugins and clicks in
- // the window. refs: #10678
- delete this._cursorToStart;
- });
- }
- if(has("webkit")){
- //WebKit sometimes doesn't fire right on selections, so the toolbar
- //doesn't update right. Therefore, help it out a bit with an additional
- //listener. A mouse up will typically indicate a display change, so fire this
- //and get the toolbar to adapt. Reference: #9532
- this._webkitListener = this.connect(this.document, "onmouseup", "onDisplayChanged");
- this.connect(this.document, "onmousedown", function(e){
- var t = e.target;
- if(t && (t === this.document.body || t === this.document)){
- // Since WebKit uses the inner DIV, we need to check and set position.
- // See: #12024 as to why the change was made.
- setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0);
- }
- });
- }
- if(has("ie")){
- // Try to make sure 'hidden' elements aren't visible in edit mode (like browsers other than IE
- // do). See #9103
- try{
- this.document.execCommand('RespectVisibilityInDesign', true, null);
- }catch(e){/* squelch */}
- }
- this.isLoaded = true;
- this.set('disabled', this.disabled); // initialize content to editable (or not)
- // Note that setValue() call will only work after isLoaded is set to true (above)
- // Set up a function to allow delaying the setValue until a callback is fired
- // This ensures extensions like dijit.Editor have a way to hold the value set
- // until plugins load (and do things like register filters).
- var setContent = lang.hitch(this, function(){
- var copyValue = this.value;
-
- this.setValue(html);
- // Defect #247392 - We added try/catch block in order to handle exception "This deferred has already been resolved".
- // This happens in Cognos Workspace (BUX), while changing tab's order on the dashdoard, which contain "Text Editor" widget(s).
- try {
- if(this.onLoadDeferred){
- this.onLoadDeferred.callback(true);
- }
- } catch (err) {
- // This error message is hard coded in DOJO in english language version only so we safely can use it to decrease the impact of code change.
- if (err.message === "This deferred has already been resolved") {
- this.setValue(copyValue);
- console.log("Caught exception: " + err.message);
- } else {
- throw err;
- }
- }
- this.onDisplayChanged();
- if(this.focusOnLoad){
- // after the document loads, then set focus after updateInterval expires so that
- // onNormalizedDisplayChanged has run to avoid input caret issues
- ready(lang.hitch(this, function(){ setTimeout(lang.hitch(this, "focus"), this.updateInterval); }));
- }
- // Save off the initial content now
- this.value = this.getValue(true);
- });
- if(this.setValueDeferred){
- this.setValueDeferred.addCallback(setContent);
- }else{
- setContent();
- }
- },
- onKeyDown: function(/* Event */ e){
- // summary:
- // Handler for onkeydown event
- // tags:
- // protected
- // we need this event at the moment to get the events from control keys
- // such as the backspace. It might be possible to add this to Dojo, so that
- // keyPress events can be emulated by the keyDown and keyUp detection.
- if(e.keyCode === keys.TAB && this.isTabIndent){
- event.stop(e); //prevent tab from moving focus out of editor
- // FIXME: this is a poor-man's indent/outdent. It would be
- // better if it added 4 " " chars in an undoable way.
- // Unfortunately pasteHTML does not prove to be undoable
- if(this.queryCommandEnabled((e.shiftKey ? "outdent" : "indent"))){
- this.execCommand((e.shiftKey ? "outdent" : "indent"));
- }
- }
- // Make tab and shift-tab skip over the <iframe>, going from the nested <div> to the toolbar
- // or next element after the editor. Needed on IE<9 and firefox.
- if(e.keyCode == keys.TAB && !this.isTabIndent){
- if(e.shiftKey && !e.ctrlKey && !e.altKey){
- // focus the <iframe> so the browser will shift-tab away from it instead
- this.beforeIframeNode.focus();
- }else if(!e.shiftKey && !e.ctrlKey && !e.altKey){
- // focus node after the <iframe> so the browser will tab away from it instead
- this.afterIframeNode.focus();
- }
- }
- if(has("ie") < 9 && e.keyCode === keys.BACKSPACE && this.document.selection.type === "Control"){
- // IE has a bug where if a non-text object is selected in the editor,
- // hitting backspace would act as if the browser's back button was
- // clicked instead of deleting the object. see #1069
- e.stopPropagation();
- e.preventDefault();
- this.execCommand("delete");
- }
- if(has("ff")){
- if(e.keyCode === keys.PAGE_UP || e.keyCode === keys.PAGE_DOWN ){
- if(this.editNode.clientHeight >= this.editNode.scrollHeight){
- // Stop the event to prevent firefox from trapping the cursor when there is no scroll bar.
- e.preventDefault();
- }
- }
- }
- return true;
- },
- onKeyUp: function(/*===== e =====*/){
- // summary:
- // Handler for onkeyup event
- // tags:
- // callback
- },
- setDisabled: function(/*Boolean*/ disabled){
- // summary:
- // Deprecated, use set('disabled', ...) instead.
- // tags:
- // deprecated
- kernel.deprecated('dijit.Editor::setDisabled is deprecated','use dijit.Editor::attr("disabled",boolean) instead', 2.0);
- this.set('disabled',disabled);
- },
- _setValueAttr: function(/*String*/ value){
- // summary:
- // Registers that attr("value", foo) should call setValue(foo)
- this.setValue(value);
- },
- _setDisableSpellCheckAttr: function(/*Boolean*/ disabled){
- if(this.document){
- domAttr.set(this.document.body, "spellcheck", !disabled);
- }else{
- // try again after the editor is finished loading
- this.onLoadDeferred.addCallback(lang.hitch(this, function(){
- domAttr.set(this.document.body, "spellcheck", !disabled);
- }));
- }
- this._set("disableSpellCheck", disabled);
- },
- onKeyPress: function(e){
- // summary:
- // Handle the various key events
- // tags:
- // protected
- if(e.keyCode === keys.SHIFT ||
- e.keyCode === keys.ALT ||
- e.keyCode === keys.META ||
- e.keyCode === keys.CTRL ||
- (e.keyCode == keys.TAB && !this.isTabIndent && !e.ctrlKey && !e.altKey)){
- return true;
- }
- var c = (e.keyChar && e.keyChar.toLowerCase()) || e.keyCode,
- handlers = this._keyHandlers[c],
- args = arguments;
-
- if(handlers && !e.altKey){
- array.some(handlers, function(h){
- // treat meta- same as ctrl-, for benefit of mac users
- if(!(h.shift ^ e.shiftKey) && !(h.ctrl ^ (e.ctrlKey||e.metaKey))){
- if(!h.handler.apply(this, args)){
- e.preventDefault();
- }
- return true;
- }
- }, this);
- }
- // function call after the character has been inserted
- if(!this._onKeyHitch){
- this._onKeyHitch = lang.hitch(this, "onKeyPressed");
- }
- setTimeout(this._onKeyHitch, 1);
- return true;
- },
- addKeyHandler: function(/*String*/ key, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Function*/ handler){
- // summary:
- // Add a handler for a keyboard shortcut
- // description:
- // The key argument should be in lowercase if it is a letter character
- // tags:
- // protected
- if(!lang.isArray(this._keyHandlers[key])){
- this._keyHandlers[key] = [];
- }
- //TODO: would be nice to make this a hash instead of an array for quick lookups
- this._keyHandlers[key].push({
- shift: shift || false,
- ctrl: ctrl || false,
- handler: handler
- });
- },
- onKeyPressed: function(){
- // summary:
- // Handler for after the user has pressed a key, and the display has been updated.
- // (Runs on a timer so that it runs after the display is updated)
- // tags:
- // private
- this.onDisplayChanged(/*e*/); // can't pass in e
- },
- onClick: function(/*Event*/ e){
- // summary:
- // Handler for when the user clicks.
- // tags:
- // private
- // console.info('onClick',this._tryDesignModeOn);
- this.onDisplayChanged(e);
- },
- _onIEMouseDown: function(){
- // summary:
- // IE only to prevent 2 clicks to focus
- // tags:
- // protected
- if(!this.focused && !this.disabled){
- this.focus();
- }
- },
- _onBlur: function(e){
- // summary:
- // Called from focus manager when focus has moved away from this editor
- // tags:
- // protected
- // console.info('_onBlur')
- this.inherited(arguments);
- var newValue = this.getValue(true);
- if(newValue !== this.value){
- this.onChange(newValue);
- }
- this._set("value", newValue);
- },
- _onFocus: function(/*Event*/ e){
- // summary:
- // Called from focus manager when focus has moved into this editor
- // tags:
- // protected
- // console.info('_onFocus')
- if(!this.disabled){
- if(!this._disabledOK){
- this.set('disabled', false);
- }
- this.inherited(arguments);
- }
- },
- // TODO: remove in 2.0
- blur: function(){
- // summary:
- // Remove focus from this instance.
- // tags:
- // deprecated
- if(!has("ie") && this.window.document.documentElement && this.window.document.documentElement.focus){
- this.window.document.documentElement.focus();
- }else if(win.doc.body.focus){
- win.doc.body.focus();
- }
- },
- focus: function(){
- // summary:
- // Move focus to this editor
- if(!this.isLoaded){
- this.focusOnLoad = true;
- return;
- }
- if(this._cursorToStart){
- delete this._cursorToStart;
- if(this.editNode.childNodes){
- this.placeCursorAtStart(); // this calls focus() so return
- return;
- }
- }
- if(has("ie") < 9){
- //this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe
- // if we fire the event manually and let the browser handle the focusing, the latest
- // cursor position is focused like in FF
- this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject/fireEvent only in IE < 11
- }else{
- // Firefox and chrome
- this.editNode.focus();
- }
- },
- // _lastUpdate: 0,
- updateInterval: 200,
- _updateTimer: null,
- onDisplayChanged: function(/*Event*/ /*===== e =====*/){
- // summary:
- // This event will be fired every time the display context
- // changes and the result needs to be reflected in the UI.
- // description:
- // If you don't want to have update too often,
- // onNormalizedDisplayChanged should be used instead
- // tags:
- // private
- // var _t=new Date();
- if(this._updateTimer){
- clearTimeout(this._updateTimer);
- }
- if(!this._updateHandler){
- this._updateHandler = lang.hitch(this,"onNormalizedDisplayChanged");
- }
- this._updateTimer = setTimeout(this._updateHandler, this.updateInterval);
- // Technically this should trigger a call to watch("value", ...) registered handlers,
- // but getValue() is too slow to call on every keystroke so we don't.
- },
- onNormalizedDisplayChanged: function(){
- // summary:
- // This event is fired every updateInterval ms or more
- // description:
- // If something needs to happen immediately after a
- // user change, please use onDisplayChanged instead.
- // tags:
- // private
- delete this._updateTimer;
- },
- onChange: function(/*===== newContent =====*/){
- // summary:
- // This is fired if and only if the editor loses focus and
- // the content is changed.
- },
- _normalizeCommand: function(/*String*/ cmd, /*Anything?*/argument){
- // summary:
- // Used as the advice function to map our
- // normalized set of commands to those supported by the target
- // browser.
- // tags:
- // private
- var command = cmd.toLowerCase();
- if(command === "formatblock"){
- if(has("safari") && argument === undefined){ command = "heading"; }
- }else if(command === "hilitecolor" && !has("mozilla")){
- command = "backcolor";
- }
- return command;
- },
- _qcaCache: {},
- queryCommandAvailable: function(/*String*/ command){
- // summary:
- // Tests whether a command is supported by the host. Clients
- // SHOULD check whether a command is supported before attempting
- // to use it, behaviour for unsupported commands is undefined.
- // command:
- // The command to test for
- // tags:
- // private
- // memoizing version. See _queryCommandAvailable for computing version
- var ca = this._qcaCache[command];
- if(ca !== undefined){ return ca; }
- return (this._qcaCache[command] = this._queryCommandAvailable(command));
- },
- _queryCommandAvailable: function(/*String*/ command){
- // summary:
- // See queryCommandAvailable().
- // tags:
- // private
- var ie = 1;
- var mozilla = 1 << 1;
- var webkit = 1 << 2;
- var opera = 1 << 3;
- function isSupportedBy(browsers){
- return {
- ie: Boolean(browsers & ie),
- mozilla: Boolean(browsers & mozilla),
- webkit: Boolean(browsers & webkit),
- opera: Boolean(browsers & opera)
- };
- }
- var supportedBy = null;
- switch(command.toLowerCase()){
- case "bold": case "italic": case "underline":
- case "subscript": case "superscript":
- case "fontname": case "fontsize":
- case "forecolor": case "hilitecolor":
- case "justifycenter": case "justifyfull": case "justifyleft":
- case "justifyright": case "delete": case "selectall": case "toggledir":
- supportedBy = isSupportedBy(mozilla | ie | webkit | opera);
- break;
- case "createlink": case "unlink": case "removeformat":
- case "inserthorizontalrule": case "insertimage":
- case "insertorderedlist": case "insertunorderedlist":
- case "indent": case "outdent": case "formatblock":
- case "inserthtml": case "undo": case "redo": case "strikethrough": case "tabindent":
- supportedBy = isSupportedBy(mozilla | ie | opera | webkit);
- break;
- case "blockdirltr": case "blockdirrtl":
- case "dirltr": case "dirrtl":
- case "inlinedirltr": case "inlinedirrtl":
- supportedBy = isSupportedBy(ie);
- break;
- case "cut": case "copy": case "paste":
- supportedBy = isSupportedBy( ie | mozilla | webkit);
- break;
- case "inserttable":
- supportedBy = isSupportedBy(mozilla | ie);
- break;
- case "insertcell": case "insertcol": case "insertrow":
- case "deletecells": case "deletecols": case "deleterows":
- case "mergecells": case "splitcell":
- supportedBy = isSupportedBy(ie | mozilla);
- break;
- default: return false;
- }
- return ((has("ie") || has("trident")) && supportedBy.ie) ||
- (has("mozilla") && supportedBy.mozilla) ||
- (has("webkit") && supportedBy.webkit) ||
- (has("opera") && supportedBy.opera); // Boolean return true if the command is supported, false otherwise
- },
- execCommand: function(/*String*/ command, argument){
- // summary:
- // Executes a command in the Rich Text area
- // command:
- // The command to execute
- // argument:
- // An optional argument to the command
- // tags:
- // protected
- var returnValue;
- //focus() is required for IE to work
- //In addition, focus() makes sure after the execution of
- //the command, the editor receives the focus as expected
- if(this.focused){
- // put focus back in the iframe, unless focus has somehow been shifted out of the editor completely
- this.focus();
- }
- command = this._normalizeCommand(command, argument);
-
- if(argument !== undefined){
- if(command === "heading"){
- throw new Error("unimplemented");
- }else if(command === "formatblock" && (has("ie") || has("trident"))){
- argument = '<'+argument+'>';
- }
- }
- //Check to see if we have any over-rides for commands, they will be functions on this
- //widget of the form _commandImpl. If we don't, fall through to the basic native
- //exec command of the browser.
- var implFunc = "_" + command + "Impl";
- if(this[implFunc]){
- returnValue = this[implFunc](argument);
- }else{
- argument = arguments.length > 1 ? argument : null;
- if(argument || command !== "createlink"){
- returnValue = this.document.execCommand(command, false, argument);
- }
- }
- this.onDisplayChanged();
- return returnValue;
- },
- queryCommandEnabled: function(/*String*/ command){
- // summary:
- // Check whether a command is enabled or not.
- // command:
- // The command to execute
- // tags:
- // protected
- if(this.disabled || !this._disabledOK){ return false; }
- command = this._normalizeCommand(command);
- //Check to see if we have any over-rides for commands, they will be functions on this
- //widget of the form _commandEnabledImpl. If we don't, fall through to the basic native
- //command of the browser.
- var implFunc = "_" + command + "EnabledImpl";
- if(this[implFunc]){
- return this[implFunc](command);
- }else{
- return this._browserQueryCommandEnabled(command);
- }
- },
- queryCommandState: function(command){
- // summary:
- // Check the state of a given command and returns true or false.
- // tags:
- // protected
- if(this.disabled || !this._disabledOK){ return false; }
- command = this._normalizeCommand(command);
- try{
- return this.document.queryCommandState(command);
- }catch(e){
- //Squelch, occurs if editor is hidden on FF 3 (and maybe others.)
- return false;
- }
- },
- queryCommandValue: function(command){
- // summary:
- // Check the value of a given command. This matters most for
- // custom selections and complex values like font value setting.
- // tags:
- // protected
- if(this.disabled || !this._disabledOK){ return false; }
- var r;
- command = this._normalizeCommand(command);
- if((has("ie") || has("trident")) && command === "formatblock"){
- r = this._native2LocalFormatNames[this.document.queryCommandValue(command)];
- }else if(has("mozilla") && command === "hilitecolor"){
- var oldValue;
- try{
- oldValue = this.document.queryCommandValue("styleWithCSS");
- }catch(e){
- oldValue = false;
- }
- this.document.execCommand("styleWithCSS", false, true);
- r = this.document.queryCommandValue(command);
- this.document.execCommand("styleWithCSS", false, oldValue);
- }else{
- r = this.document.queryCommandValue(command);
- }
- return r;
- },
- // Misc.
- _sCall: function(name, args){
- // summary:
- // Run the named method of dijit._editor.selection over the
- // current editor instance's window, with the passed args.
- // tags:
- // private
- return win.withGlobal(this.window, name, selectionapi, args);
- },
- // FIXME: this is a TON of code duplication. Why?
- placeCursorAtStart: function(){
- // summary:
- // Place the cursor at the start of the editing area.
- // tags:
- // private
- this.focus();
- //see comments in placeCursorAtEnd
- var isvalid=false;
- if(has("mozilla")){
- // TODO: Is this branch even necessary?
- var first=this.editNode.firstChild;
- while(first){
- if(first.nodeType === 3){
- if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
- isvalid=true;
- this._sCall("selectElement", [ first ]);
- break;
- }
- }else if(first.nodeType === 1){
- isvalid=true;
- var tg = first.tagName ? first.tagName.toLowerCase() : "";
- // Collapse before childless tags.
- if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){
- this._sCall("selectElement", [ first ]);
- }else{
- // Collapse inside tags with children.
- this._sCall("selectElementChildren", [ first ]);
- }
- break;
- }
- first = first.nextSibling;
- }
- }else{
- isvalid=true;
- this._sCall("selectElementChildren", [ this.editNode ]);
- }
- if(isvalid){
- this._sCall("collapse", [ true ]);
- }
- },
- placeCursorAtEnd: function(){
- // summary:
- // Place the cursor at the end of the editing area.
- // tags:
- // private
- this.focus();
- //In mozilla, if last child is not a text node, we have to use
- // selectElementChildren on this.editNode.lastChild otherwise the
- // cursor would be placed at the end of the closing tag of
- //this.editNode.lastChild
- var isvalid=false;
- if(has("mozilla")){
- var last=this.editNode.lastChild;
- while(last){
- if(last.nodeType === 3){
- if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
- isvalid=true;
- this._sCall("selectElement", [ last ]);
- break;
- }
- }else if(last.nodeType === 1){
- isvalid=true;
- if(last.lastChild){
- this._sCall("selectElement", [ last.lastChild ]);
- }else{
- this._sCall("selectElement", [ last ]);
- }
- break;
- }
- last = last.previousSibling;
- }
- }else{
- isvalid=true;
- this._sCall("selectElementChildren", [ this.editNode ]);
- }
- if(isvalid){
- this._sCall("collapse", [ false ]);
- }
- },
- getValue: function(/*Boolean?*/ nonDestructive){
- // summary:
- // Return the current content of the editing area (post filters
- // are applied). Users should call get('value') instead.
- // nonDestructive:
- // defaults to false. Should the post-filtering be run over a copy
- // of the live DOM? Most users should pass "true" here unless they
- // *really* know that none of the installed filters are going to
- // mess up the editing session.
- // tags:
- // private
- if(this.textarea){
- if(this.isClosed || !this.isLoaded){
- return this.textarea.value;
- }
- }
- return this._postFilterContent(null, nonDestructive);
- },
- _getValueAttr: function(){
- // summary:
- // Hook to make attr("value") work
- return this.getValue(true);
- },
- setValue: function(/*String*/ html){
- // summary:
- // This function sets the content. No undo history is preserved.
- // Users should use set('value', ...) instead.
- // tags:
- // deprecated
- // TODO: remove this and getValue() for 2.0, and move code to _setValueAttr()
- if(!this.isLoaded){
- // try again after the editor is finished loading
- this.onLoadDeferred.addCallback(lang.hitch(this, function(){
- this.setValue(html);
- }));
- return;
- }
- this._cursorToStart = true;
- if(this.textarea && (this.isClosed || !this.isLoaded)){
- this.textarea.value=html;
- }else{
- html = this._preFilterContent(html);
- var node = this.isClosed ? this.domNode : this.editNode;
- // Use to avoid webkit problems where editor is disabled until the user clicks it
- if(!html && has("webkit")){
- html = " "; //
- }
- node.innerHTML = html;
- this._preDomFilterContent(node);
- }
- this.onDisplayChanged();
- this._set("value", this.getValue(true));
- },
- replaceValue: function(/*String*/ html){
- // summary:
- // This function set the content while trying to maintain the undo stack
- // (now only works fine with Moz, this is identical to setValue in all
- // other browsers)
- // tags:
- // protected
- if(this.isClosed){
- this.setValue(html);
- }else if(this.window && this.window.getSelection && !has("mozilla")){ // Safari
- // look ma! it's a totally f'd browser!
- this.setValue(html);
- }else if(this.window && this.window.getSelection){ // Moz
- html = this._preFilterContent(html);
- this.execCommand("selectall");
- this.execCommand("inserthtml", html);
- this._preDomFilterContent(this.editNode);
- }else if(this.document && this.document.selection){//IE
- //In IE, when the first element is not a text node, say
- //an <a> tag, when replacing the content of the editing
- //area, the <a> tag will be around all the content
- //so for now, use setValue for IE too
- this.setValue(html);
- }
- this._set("value", this.getValue(true));
- },
- _preFilterContent: function(/*String*/ html){
- // summary:
- // Filter the input before setting the content of the editing
- // area. DOM pre-filtering may happen after this
- // string-based filtering takes place but as of 1.2, this is not
- // guaranteed for operations such as the inserthtml command.
- // tags:
- // private
- var ec = html;
- array.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
- return ec;
- },
- _preDomFilterContent: function(/*DomNode*/ dom){
- // summary:
- // filter the input's live DOM. All filter operations should be
- // considered to be "live" and operating on the DOM that the user
- // will be interacting with in their editing session.
- // tags:
- // private
- dom = dom || this.editNode;
- array.forEach(this.contentDomPreFilters, function(ef){
- if(ef && lang.isFunction(ef)){
- ef(dom);
- }
- }, this);
- },
- _postFilterContent: function(
- /*DomNode|DomNode[]|String?*/ dom,
- /*Boolean?*/ nonDestructive){
- // summary:
- // filter the output after getting the content of the editing area
- //
- // description:
- // post-filtering allows plug-ins and users to specify any number
- // of transforms over the editor's content, enabling many common
- // use-cases such as transforming absolute to relative URLs (and
- // vice-versa), ensuring conformance with a particular DTD, etc.
- // The filters are registered in the contentDomPostFilters and
- // contentPostFilters arrays. Each item in the
- // contentDomPostFilters array is a function which takes a DOM
- // Node or array of nodes as its only argument and returns the
- // same. It is then passed down the chain for further filtering.
- // The contentPostFilters array behaves the same way, except each
- // member operates on strings. Together, the DOM and string-based
- // filtering allow the full range of post-processing that should
- // be necessaray to enable even the most agressive of post-editing
- // conversions to take place.
- //
- // If nonDestructive is set to "true", the nodes are cloned before
- // filtering proceeds to avoid potentially destructive transforms
- // to the content which may still needed to be edited further.
- // Once DOM filtering has taken place, the serialized version of
- // the DOM which is passed is run through each of the
- // contentPostFilters functions.
- //
- // dom:
- // a node, set of nodes, which to filter using each of the current
- // members of the contentDomPostFilters and contentPostFilters arrays.
- //
- // nonDestructive:
- // defaults to "false". If true, ensures that filtering happens on
- // a clone of the passed-in content and not the actual node
- // itself.
- //
- // tags:
- // private
- var ec;
- if(!lang.isString(dom)){
- dom = dom || this.editNode;
- if(this.contentDomPostFilters.length){
- if(nonDestructive){
- dom = lang.clone(dom);
- }
- array.forEach(this.contentDomPostFilters, function(ef){
- dom = ef(dom);
- });
- }
- ec = htmlapi.getChildrenHtml(dom);
- }else{
- ec = dom;
- }
- if(!lang.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){
- ec = "";
- }
- // if(has("ie")){
- // //removing appended <P> </P> for IE
- // ec = ec.replace(/(?:<p> </p>[\n\r]*)+$/i,"");
- // }
- array.forEach(this.contentPostFilters, function(ef){
- ec = ef(ec);
- });
- return ec;
- },
- _saveContent: function(){
- // summary:
- // Saves the content in an onunload event if the editor has not been closed
- // tags:
- // private
- var saveTextarea = dom.byId(dijit._scopeName + "._editor.BuxRichText.value");
- if(saveTextarea){
- if(saveTextarea.value){
- saveTextarea.value += this._SEPARATOR;
- }
- saveTextarea.value += this.name + this._NAME_CONTENT_SEP + this.getValue(true);
- }
- },
- escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){
- // summary:
- // Adds escape sequences for special characters in XML.
- // Optionally skips escapes for single quotes
- // tags:
- // private
- str = str.replace(/&/gm, "&").replace(/</gm, "<").replace(/>/gm, ">").replace(/"/gm, """);
- if(!noSingleQuotes){
- str = str.replace(/'/gm, "'");
- }
- return str; // string
- },
- getNodeHtml: function(/* DomNode */ node){
- // summary:
- // Deprecated. Use dijit/_editor/html::_getNodeHtml() instead.
- // tags:
- // deprecated
- kernel.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit/_editor/html::getNodeHtml instead', 2);
- return htmlapi.getNodeHtml(node); // String
- },
- getNodeChildrenHtml: function(/* DomNode */ dom){
- // summary:
- // Deprecated. Use dijit/_editor/html::getChildrenHtml() instead.
- // tags:
- // deprecated
- kernel.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit/_editor/html::getChildrenHtml instead', 2);
- return htmlapi.getChildrenHtml(dom);
- },
- close: function(/*Boolean?*/ save){
- // summary:
- // Kills the editor and optionally writes back the modified contents to the
- // element from which it originated.
- // save:
- // Whether or not to save the changes. If false, the changes are discarded.
- // tags:
- // private
- if(this.isClosed){ return; }
- if(!arguments.length){ save = true; }
- if(save){
- this._set("value", this.getValue(true));
- }
- // line height is squashed for iframes
- // FIXME: why was this here? if(this.iframe){ this.domNode.style.lineHeight = null; }
- if(this.interval){ clearInterval(this.interval); }
- if(this._webkitListener){
- //Cleaup of WebKit fix: #9532
- this.disconnect(this._webkitListener);
- delete this._webkitListener;
- }
- // Guard against memory leaks on IE (see #9268)
- if(has("ie")){
- this.iframe.onfocus = null;
- }
- this.iframe._loadFunc = null;
- if(this._iframeRegHandle){
- this._iframeRegHandle.remove();
- delete this._iframeRegHandle;
- }
- if(this.textarea){
- var s = this.textarea.style;
- s.position = "";
- s.left = s.top = "";
- if(has("ie")){
- s.overflow = this.__overflow;
- this.__overflow = null;
- }
- this.textarea.value = this.value;
- domConstruct.destroy(this.domNode);
- this.domNode = this.textarea;
- }else{
- // Note that this destroys the iframe
- this.domNode.innerHTML = this.value;
- }
- delete this.iframe;
- domClass.remove(this.domNode, this.baseClass);
- this.isClosed = true;
- this.isLoaded = false;
- delete this.editNode;
- delete this.focusNode;
- if(this.window && this.window._frameElement){
- this.window._frameElement = null;
- }
- this.window = null;
- this.document = null;
- this.editingArea = null;
- this.editorObject = null;
- },
- destroy: function(){
- if(!this.isClosed){ this.close(false); }
- if(this._updateTimer){
- clearTimeout(this._updateTimer);
- }
- this.inherited(arguments);
- if(BuxRichText._globalSaveHandler){
- delete BuxRichText._globalSaveHandler[this.id];
- }
- },
- _removeMozBogus: function(/* String */ html){
- // summary:
- // Post filter to remove unwanted HTML attributes generated by mozilla
- // tags:
- // private
- return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, '').replace(/_moz_resizing="(true|false)"/gi,''); // String
- },
- _removeWebkitBogus: function(/* String */ html){
- // summary:
- // Post filter to remove unwanted HTML attributes generated by webkit
- // tags:
- // private
- html = html.replace(/\sclass="webkit-block-placeholder"/gi, '');
- html = html.replace(/\sclass="apple-style-span"/gi, '');
- // For some reason copy/paste sometime adds extra meta tags for charset on
- // webkit (chrome) on mac.They need to be removed. See: #12007"
- html = html.replace(/<meta charset=\"utf-8\" \/>/gi, '');
- return html; // String
- },
- _normalizeFontStyle: function(/* String */ html){
- // summary:
- // Convert 'strong' and 'em' to 'b' and 'i'.
- // description:
- // Moz can not handle strong/em tags correctly, so to help
- // mozilla and also to normalize output, convert them to 'b' and 'i'.
- //
- // Note the IE generates 'strong' and 'em' rather than 'b' and 'i'
- // tags:
- // private
- return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
- .replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
- },
- _preFixUrlAttributes: function(/* String */ html){
- // summary:
- // Pre-filter to do fixing to href attributes on <a> and <img> tags
- // tags:
- // private
- return html.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi,
- '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
- .replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi,
- '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
- },
- /*****************************************************************************
- The following functions implement HTML manipulation commands for various
- browser/contentEditable implementations. The goal of them is to enforce
- standard behaviors of them.
- ******************************************************************************/
- /*** queryCommandEnabled implementations ***/
- _browserQueryCommandEnabled: function(command){
- // summary:
- // Implementation to call to the native queryCommandEnabled of the browser.
- // command:
- // The command to check.
- // tags:
- // protected
- if(!command) { return false; }
- var elem = has("ie") < 9 ? this.document.selection.createRange() : this.document;
- try{
- return elem.queryCommandEnabled(command);
- }catch(e){
- return false;
- }
- },
- _createlinkEnabledImpl: function(/*===== argument =====*/){
- // summary:
- // This function implements the test for if the create link
- // command should be enabled or not.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var enabled = true;
- if(has("opera")){
- var sel = this.window.getSelection();
- if(sel.isCollapsed){
- enabled = true;
- }else{
- enabled = this.document.queryCommandEnabled("createlink");
- }
- }else{
- enabled = this._browserQueryCommandEnabled("createlink");
- }
- return enabled;
- },
- _unlinkEnabledImpl: function(/*===== argument =====*/){
- // summary:
- // This function implements the test for if the unlink
- // command should be enabled or not.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var enabled = true;
- if(has("mozilla") || has("webkit")){
- enabled = this._sCall("hasAncestorElement", ["a"]);
- }else{
- enabled = this._browserQueryCommandEnabled("unlink");
- }
- return enabled;
- },
- _inserttableEnabledImpl: function(/*===== argument =====*/){
- // summary:
- // This function implements the test for if the inserttable
- // command should be enabled or not.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var enabled = true;
- if(has("mozilla") || has("webkit")){
- enabled = true;
- }else{
- enabled = this._browserQueryCommandEnabled("inserttable");
- }
- return enabled;
- },
- _cutEnabledImpl: function(/*===== argument =====*/){
- // summary:
- // This function implements the test for if the cut
- // command should be enabled or not.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var enabled = true;
- if(has("webkit")){
- // WebKit deems clipboard activity as a security threat and natively would return false
- var sel = this.window.getSelection();
- if(sel){ sel = sel.toString(); }
- enabled = !!sel;
- }else{
- enabled = this._browserQueryCommandEnabled("cut");
- }
- return enabled;
- },
- _copyEnabledImpl: function(/*===== argument =====*/){
- // summary:
- // This function implements the test for if the copy
- // command should be enabled or not.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var enabled = true;
- if(has("webkit")){
- // WebKit deems clipboard activity as a security threat and natively would return false
- var sel = this.window.getSelection();
- if(sel){ sel = sel.toString(); }
- enabled = !!sel;
- }else{
- enabled = this._browserQueryCommandEnabled("copy");
- }
- return enabled;
- },
- _pasteEnabledImpl: function(/*===== argument =====*/){
- // summary:c
- // This function implements the test for if the paste
- // command should be enabled or not.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var enabled = true;
- if(has("webkit")){
- return true;
- }else{
- enabled = this._browserQueryCommandEnabled("paste");
- }
- return enabled;
- },
- /*** execCommand implementations ***/
- _inserthorizontalruleImpl: function(argument){
- // summary:
- // This function implements the insertion of HTML 'HR' tags.
- // into a point on the page. IE doesn't to it right, so
- // we have to use an alternate form
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if(has("ie")){
- return this._inserthtmlImpl("<hr>");
- }
- return this.document.execCommand("inserthorizontalrule", false, argument);
- },
- _unlinkImpl: function(argument){
- // summary:
- // This function implements the unlink of an 'a' tag.
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if((this.queryCommandEnabled("unlink")) && (has("mozilla") || has("webkit"))){
- var a = this._sCall("getAncestorElement", [ "a" ]);
- this._sCall("selectElement", [ a ]);
- return this.document.execCommand("unlink", false, null);
- }
- return this.document.execCommand("unlink", false, argument);
- },
- _hilitecolorImpl: function(argument){
- // summary:
- // This function implements the hilitecolor command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var returnValue;
- var isApplied = this._handleTextColorOrProperties("hilitecolor", argument);
- if(!isApplied){
- if(has("mozilla")){
- // mozilla doesn't support hilitecolor properly when useCSS is
- // set to false (bugzilla #279330)
- this.document.execCommand("styleWithCSS", false, true);
- console.log("Executing color command.");
- returnValue = this.document.execCommand("hilitecolor", false, argument);
- this.document.execCommand("styleWithCSS", false, false);
- }else{
- returnValue = this.document.execCommand("hilitecolor", false, argument);
- }
- }
- return returnValue;
- },
- _backcolorImpl: function(argument){
- // summary:
- // This function implements the backcolor command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if(has("ie")){
- // Tested under IE 6 XP2, no problem here, comment out
- // IE weirdly collapses ranges when we exec these commands, so prevent it
- // var tr = this.document.selection.createRange();
- argument = argument ? argument : null;
- }
- var isApplied = this._handleTextColorOrProperties("backcolor", argument);
- if(!isApplied){
- isApplied = this.document.execCommand("backcolor", false, argument);
- }
- return isApplied;
- },
- _forecolorImpl: function(argument){
- // summary:
- // This function implements the forecolor command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- if(has("ie")){
- // Tested under IE 6 XP2, no problem here, comment out
- // IE weirdly collapses ranges when we exec these commands, so prevent it
- // var tr = this.document.selection.createRange();
- argument = argument? argument : null;
- }
- var isApplied = false;
- isApplied = this._handleTextColorOrProperties("forecolor", argument);
- if(!isApplied){
- isApplied = this.document.execCommand("forecolor", false, argument);
- }
- return isApplied;
- },
- _inserthtmlImpl: function(argument){
- // summary:
- // This function implements the insertion of HTML content into
- // a point on the page.
- // argument:
- // The content to insert, if any.
- // tags:
- // protected
- argument = this._preFilterContent(argument);
- var rv = true;
- if(has("ie") < 9){
- var insertRange = this.document.selection.createRange();
- if(this.document.selection.type.toUpperCase() === 'CONTROL'){
- var n = insertRange.item(0);
- while(insertRange.length){
- insertRange.remove(insertRange.item(0));
- }
- n.outerHTML = argument;
- }else{
- insertRange.pasteHTML(argument);
- }
- insertRange.select();
- }else if(has("trident") < 8){
- var insertRange;
- var selection = rangeapi.getSelection(this.window);
- if(selection && selection.rangeCount && selection.getRangeAt){
- insertRange = selection.getRangeAt(0);
- insertRange.deleteContents();
- var div = domConstruct.create('div');
- div.innerHTML = argument;
- var node, lastNode;
- var n = this.document.createDocumentFragment();
- while((node = div.firstChild)){
- lastNode = n.appendChild(node);
- }
- insertRange.insertNode(n);
- if(lastNode) {
- insertRange = insertRange.cloneRange();
- insertRange.setStartAfter(lastNode);
- insertRange.collapse(false);
- selection.removeAllRanges();
- selection.addRange(insertRange);
- }
- }
- }else if(has("mozilla") && !argument.length){
- //mozilla can not inserthtml an empty html to delete current selection
- //so we delete the selection instead in this case
- this._sCall("remove"); // FIXME
- }else{
- rv = this.document.execCommand("inserthtml", false, argument);
- }
- return rv;
- },
- _boldImpl: function(argument){
- // summary:
- // This function implements an over-ride of the bold command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- this._adaptIESelection();
- applied = this._adaptIEFormatAreaAndExec("bold");
- }
- if(!applied){
- applied = this.document.execCommand("bold", false, argument);
- }
- return applied;
- },
- _italicImpl: function(argument){
- // summary:
- // This function implements an over-ride of the italic command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- this._adaptIESelection();
- applied = this._adaptIEFormatAreaAndExec("italic");
- }
- if(!applied){
- applied = this.document.execCommand("italic", false, argument);
- }
- return applied;
- },
- _underlineImpl: function(argument){
- // summary:
- // This function implements an over-ride of the underline command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- this._adaptIESelection();
- applied = this._adaptIEFormatAreaAndExec("underline");
- }
- if(!applied){
- applied = this.document.execCommand("underline", false, argument);
- }
- return applied;
- },
- _strikethroughImpl: function(argument){
- // summary:
- // This function implements an over-ride of the strikethrough command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- this._adaptIESelection();
- applied = this._adaptIEFormatAreaAndExec("strikethrough");
- }
- if(!applied){
- applied = this.document.execCommand("strikethrough", false, argument);
- }
- return applied;
- },
- _superscriptImpl: function(argument){
- // summary:
- // This function implements an over-ride of the superscript command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- this._adaptIESelection();
- applied = this._adaptIEFormatAreaAndExec("superscript");
- }
- if(!applied){
- applied = this.document.execCommand("superscript", false, argument);
- }
- return applied;
- },
- _subscriptImpl: function(argument){
- // summary:
- // This function implements an over-ride of the superscript command.
- // argument:
- // Not used, operates by selection.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- this._adaptIESelection();
- applied = this._adaptIEFormatAreaAndExec("subscript");
-
- }
- if(!applied){
- applied = this.document.execCommand("subscript", false, argument);
- }
- return applied;
- },
-
- _fontnameImpl: function(argument){
- // summary:
- // This function implements the fontname command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var isApplied;
- if(has("ie") || has("trident")){
- isApplied = this._handleTextColorOrProperties("fontname", argument);
- }
- if(!isApplied){
- isApplied = this.document.execCommand("fontname", false, argument);
- }
- return isApplied;
- },
- _fontsizeImpl: function(argument){
- // summary:
- // This function implements the fontsize command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var isApplied;
- if(has("ie") || has("trident")){
- isApplied = this._handleTextColorOrProperties("fontsize", argument);
- }
- if(!isApplied){
- isApplied = this.document.execCommand("fontsize", false, argument);
- }
- return isApplied;
- },
-
- _insertorderedlistImpl: function(argument){
- // summary:
- // This function implements the insertorderedlist command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- applied = this._adaptIEList("insertorderedlist", argument);
- }
- if(!applied){
- applied = this.document.execCommand("insertorderedlist", false, argument);
- }
- return applied;
- },
-
- _insertunorderedlistImpl: function(argument){
- // summary:
- // This function implements the insertunorderedlist command
- // argument:
- // arguments to the exec command, if any.
- // tags:
- // protected
- var applied = false;
- if(has("ie") || has("trident")){
- applied = this._adaptIEList("insertunorderedlist", argument);
- }
- if(!applied){
- applied = this.document.execCommand("insertunorderedlist", false, argument);
- }
- return applied;
- },
-
- getHeaderHeight: function(){
- // summary:
- // A function for obtaining the height of the header node
- return this._getNodeChildrenHeight(this.header); // Number
- },
- getFooterHeight: function(){
- // summary:
- // A function for obtaining the height of the footer node
- return this._getNodeChildrenHeight(this.footer); // Number
- },
- _getNodeChildrenHeight: function(node){
- // summary:
- // An internal function for computing the cumulative height of all child nodes of 'node'
- // node:
- // The node to process the children of;
- var h = 0;
- if(node && node.childNodes){
- // IE didn't compute it right when position was obtained on the node directly is some cases,
- // so we have to walk over all the children manually.
- var i;
- for(i = 0; i < node.childNodes.length; i++){
- var size = domGeometry.position(node.childNodes[i]);
- h += size.h;
- }
- }
- return h; // Number
- },
- _isNodeEmpty: function(node, startOffset){
- // summary:
- // Function to test if a node is devoid of real content.
- // node:
- // The node to check.
- // tags:
- // private.
- if(node.nodeType === 1/*element*/){
- if(node.childNodes.length > 0){
- return this._isNodeEmpty(node.childNodes[0], startOffset);
- }
- return true;
- }else if(node.nodeType === 3/*text*/){
- return (node.nodeValue.substring(startOffset) === "");
- }
- return false;
- },
- _removeStartingRangeFromRange: function(node, range){
- // summary:
- // Function to adjust selection range by removing the current
- // start node.
- // node:
- // The node to remove from the starting range.
- // range:
- // The range to adapt.
- // tags:
- // private
- if(node.nextSibling){
- range.setStart(node.nextSibling,0);
- }else{
- var parent = node.parentNode;
- while(parent && parent.nextSibling == null){
- //move up the tree until we find a parent that has another node, that node will be the next node
- parent = parent.parentNode;
- }
- if(parent){
- range.setStart(parent.nextSibling,0);
- }
- }
- return range;
- },
- _adaptIESelection: function(){
- // summary:
- // Function to adapt the IE range by removing leading 'newlines'
- // Needed to fix issue with bold/italics/underline not working if
- // range included leading 'newlines'.
- // In IE, if a user starts a selection at the very end of a line,
- // then the native browser commands will fail to execute correctly.
- // To work around the issue, we can remove all empty nodes from
- // the start of the range selection.
- var selection = rangeapi.getSelection(this.window);
- if(selection && selection.rangeCount && !selection.isCollapsed){
- var range = selection.getRangeAt(0);
- var firstNode = range.startContainer;
- var startOffset = range.startOffset;
- while(firstNode.nodeType === 3/*text*/ && startOffset >= firstNode.length && firstNode.nextSibling){
- //traverse the text nodes until we get to the one that is actually highlighted
- startOffset = startOffset - firstNode.length;
- firstNode = firstNode.nextSibling;
- }
- //Remove the starting ranges until the range does not start with an empty node.
- var lastNode=null;
- while(this._isNodeEmpty(firstNode, startOffset) && firstNode !== lastNode){
- lastNode =firstNode; //this will break the loop in case we can't find the next sibling
- range = this._removeStartingRangeFromRange(firstNode, range); //move the start container to the next node in the range
- firstNode = range.startContainer;
- startOffset = 0; //start at the beginning of the new starting range
- }
- selection.removeAllRanges();// this will work as long as users cannot select multiple ranges. I have not been able to do that in the editor.
- selection.addRange(range);
- }
- },
-
- _adaptIEFormatAreaAndExec: function(command){
- // summary:
- // Function to handle IE's quirkiness regarding how it handles
- // format commands on a word. This involves a lit of node splitting
- // and format cloning.
- // command:
- // The format command, needed to check if the desired
- // command is true or not.
- var selection = rangeapi.getSelection(this.window);
- var doc = this.document;
- var rs, ret, range, txt, startNode, endNode, breaker, sNode;
- if(command && selection && selection.isCollapsed){
- var isApplied = this.queryCommandValue(command);
- if(isApplied){
-
- // We have to split backwards until we hit the format
- var nNames = this._tagNamesForCommand(command);
- range = selection.getRangeAt(0);
- var fs = range.startContainer;
- if(fs.nodeType === 3){
- var offset = range.endOffset;
- if(fs.length < offset){
- //We are not looking from the right node, try to locate the correct one
- ret = this._adjustNodeAndOffset(rs, offset);
- fs = ret.node;
- offset = ret.offset;
- }
- }
- var topNode;
- while(fs && fs !== this.editNode){
- // We have to walk back and see if this is still a format or not.
- // Hm, how do I do this?
- var tName = fs.tagName? fs.tagName.toLowerCase() : "";
- if(array.indexOf(nNames, tName) > -1){
- topNode = fs;
- break;
- }
- fs = fs.parentNode;
- }
- // Okay, we have a stopping place, time to split things apart.
- if(topNode){
- // Okay, we know how far we have to split backwards, so we have to split now.
- rs = range.startContainer;
- var newblock = doc.createElement(topNode.tagName);
- domConstruct.place(newblock, topNode, "after");
- if(rs && rs.nodeType === 3){
- // Text node, we have to split it.
- var nodeToMove, tNode;
- var endOffset = range.endOffset;
- if(rs.length < endOffset){
- //We are not splitting the right node, try to locate the correct one
- ret = this._adjustNodeAndOffset(rs, endOffset);
- rs = ret.node;
- endOffset = ret.offset;
- }
-
- txt = rs.nodeValue;
- startNode = doc.createTextNode(txt.substring(0, endOffset));
- var endText = txt.substring(endOffset, txt.length);
- if(endText){
- endNode = doc.createTextNode(endText);
- }
- // Place the split, then remove original nodes.
- domConstruct.place(startNode, rs, "before");
- if(endNode){
- breaker = doc.createElement("span");
- breaker.className = "ieFormatBreakerSpan";
- domConstruct.place(breaker, rs, "after");
- domConstruct.place(endNode, breaker, "after");
- endNode = breaker;
- }
- domConstruct.destroy(rs);
-
- // Okay, we split the text. Now we need to see if we're
- // parented to the block element we're splitting and if
- // not, we have to split all the way up. Ugh.
- var parentC = startNode.parentNode;
- var tagList = [];
- var tagData;
- while(parentC !== topNode){
- var tg = parentC.tagName;
- tagData = {tagName: tg};
- tagList.push(tagData);
-
- var newTg = doc.createElement(tg);
- // Clone over any 'style' data.
- if(parentC.style){
- if(newTg.style){
- if(parentC.style.cssText){
- newTg.style.cssText = parentC.style.cssText;
- tagData.cssText = parentC.style.cssText;
- }
- }
- }
- // If font also need to clone over any font data.
- if(parentC.tagName === "FONT"){
- if(parentC.color){
- newTg.color = parentC.color;
- tagData.color = parentC.color;
- }
- if(parentC.face){
- newTg.face = parentC.face;
- tagData.face = parentC.face;
- }
- if(parentC.size){ // this check was necessary on IE
- newTg.size = parentC.size;
- tagData.size = parentC.size;
- }
- }
- if(parentC.className){
- newTg.className = parentC.className;
- tagData.className = parentC.className;
- }
-
- // Now move end node and every sibling
- // after it over into the new tag.
- if(endNode){
- nodeToMove = endNode;
- while(nodeToMove){
- tNode = nodeToMove.nextSibling;
- newTg.appendChild(nodeToMove);
- nodeToMove = tNode;
- }
- }
- if(newTg.tagName == parentC.tagName){
- breaker = doc.createElement("span");
- breaker.className = "ieFormatBreakerSpan";
- domConstruct.place(breaker, parentC, "after");
- domConstruct.place(newTg, breaker, "after");
- }else{
- domConstruct.place(newTg, parentC, "after");
- }
- startNode = parentC;
- endNode = newTg;
- parentC = parentC.parentNode;
- }
- // Lastly, move the split out all the split tags
- // to the new block as they should now be split properly.
- if(endNode){
- nodeToMove = endNode;
- if(nodeToMove.nodeType === 1 || (nodeToMove.nodeType === 3 && nodeToMove.nodeValue)){
- // Non-blank text and non-text nodes need to clear out that blank space
- // before moving the contents.
- newblock.innerHTML = "";
- }
- while(nodeToMove){
- tNode = nodeToMove.nextSibling;
- newblock.appendChild(nodeToMove);
- nodeToMove = tNode;
- }
- }
-
- // We had intermediate tags, we have to now recreate them inbetween the split
- // and restore what styles, classnames, etc, we can.
- if(tagList.length){
- tagData = tagList.pop();
- var newContTag = doc.createElement(tagData.tagName);
- if(tagData.cssText && newContTag.style){
- newContTag.style.cssText = tagData.cssText;
- }
- if(tagData.className){
- newContTag.className = tagData.className;
- }
- if(tagData.tagName === "FONT"){
- if(tagData.color){
- newContTag.color = tagData.color;
- }
- if(tagData.face){
- newContTag.face = tagData.face;
- }
- if(tagData.size){
- newContTag.size = tagData.size;
- }
- }
- domConstruct.place(newContTag, newblock, "before");
- while(tagList.length){
- tagData = tagList.pop();
- var newTgNode = doc.createElement(tagData.tagName);
- if(tagData.cssText && newTgNode.style){
- newTgNode.style.cssText = tagData.cssText;
- }
- if(tagData.className){
- newTgNode.className = tagData.className;
- }
- if(tagData.tagName === "FONT"){
- if(tagData.color){
- newTgNode.color = tagData.color;
- }
- if(tagData.face){
- newTgNode.face = tagData.face;
- }
- if(tagData.size){
- newTgNode.size = tagData.size;
- }
- }
- newContTag.appendChild(newTgNode);
- newContTag = newTgNode;
- }
-
- // Okay, everything is theoretically split apart and removed from the content
- // so insert the dummy text to select, select it, then
- // clear to position cursor.
- sNode = doc.createTextNode(".");
- breaker.appendChild(sNode);
- newContTag.appendChild(sNode);
- win.withGlobal(this.window, lang.hitch(this, function(){
- var newrange = rangeapi.create();
- newrange.setStart(sNode, 0);
- newrange.setEnd(sNode, sNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- selectionapi.collapse(false);
- sNode.parentNode.innerHTML = "";
- }));
- }else{
- // No extra tags, so we have to insert a breaker point and rely
- // on filters to remove it later.
- breaker = doc.createElement("span");
- breaker.className="ieFormatBreakerSpan";
- sNode = doc.createTextNode(".");
- breaker.appendChild(sNode);
- domConstruct.place(breaker, newblock, "before");
- win.withGlobal(this.window, lang.hitch(this, function(){
- var newrange = rangeapi.create();
- newrange.setStart(sNode, 0);
- newrange.setEnd(sNode, sNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- selectionapi.collapse(false);
- sNode.parentNode.innerHTML = "";
- }));
- }
- if(!newblock.firstChild){
- // Empty, we don't need it. Split was at end or similar
- // So, remove it.
- domConstruct.destroy(newblock);
- }
- return true;
- }
- }
- return false;
- }else{
- range = selection.getRangeAt(0);
- rs = range.startContainer;
- if(rs && rs.nodeType === 3){
- // Text node, we have to split it.
- win.withGlobal(this.window, lang.hitch(this, function(){
- var offset = range.startOffset;
- if(rs.length < offset){
- //We are not splitting the right node, try to locate the correct one
- ret = this._adjustNodeAndOffset(rs, offset);
- rs = ret.node;
- offset = ret.offset;
- }
- txt = rs.nodeValue;
- startNode = doc.createTextNode(txt.substring(0, offset));
- var endText = txt.substring(offset);
- if(endText !== ""){
- endNode = doc.createTextNode(txt.substring(offset));
- }
- // Create a space, we'll select and bold it, so
- // the whole word doesn't get bolded
- breaker = doc.createElement("span");
- sNode = doc.createTextNode(".");
- breaker.appendChild(sNode);
- if(startNode.length){
- domConstruct.place(startNode, rs, "after");
- }else{
- startNode = rs;
- }
- domConstruct.place(breaker, startNode, "after");
- if(endNode){
- domConstruct.place(endNode, breaker, "after");
- }
- domConstruct.destroy(rs);
- var newrange = rangeapi.create();
- newrange.setStart(sNode, 0);
- newrange.setEnd(sNode, sNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- doc.execCommand(command);
- domConstruct.place(breaker.firstChild, breaker, "before");
- domConstruct.destroy(breaker);
- newrange.setStart(sNode, 0);
- newrange.setEnd(sNode, sNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- selectionapi.collapse(false);
- sNode.parentNode.innerHTML = "";
- }));
- return true;
- }
- }
- }else{
- return false;
- }
- },
-
- _adaptIEList: function(command /*===== , argument =====*/){
- // summary:
- // This function handles normalizing the IE list behavior as
- // much as possible.
- // command:
- // The list command to execute.
- // argument:
- // Any additional argument.
- // tags:
- // private
- var selection = rangeapi.getSelection(this.window);
- if(selection.isCollapsed){
- // In the case of no selection, lets commonize the behavior and
- // make sure that it indents if needed.
- if(selection.rangeCount && !this.queryCommandValue(command)){
- var range = selection.getRangeAt(0);
- var sc = range.startContainer;
- if(sc && sc.nodeType == 3){
- // text node. Lets see if there is a node before it that isn't
- // some sort of breaker.
- if(!range.startOffset){
- // We're at the beginning of a text area. It may have been br split
- // Who knows? In any event, we must create the list manually
- // or IE may shove too much into the list element. It seems to
- // grab content before the text node too if it's br split.
- // Why can't IE work like everyone else?
- win.withGlobal(this.window, lang.hitch(this, function(){
- // Create a space, we'll select and bold it, so
- // the whole word doesn't get bolded
- var lType = "ul";
- if(command === "insertorderedlist"){
- lType = "ol";
- }
- var list = domConstruct.create(lType);
- var li = domConstruct.create("li", null, list);
- domConstruct.place(list, sc, "before");
- // Move in the text node as part of the li.
- li.appendChild(sc);
- // We need a br after it or the enter key handler
- // sometimes throws errors.
- domConstruct.create("br", null, list, "after");
- // Okay, now lets move our cursor to the beginning.
- var newrange = rangeapi.create();
- newrange.setStart(sc, 0);
- newrange.setEnd(sc, sc.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- selectionapi.collapse(true);
- }));
- return true;
- }
- }
- }
- }
- return false;
- },
-
- _handleTextColorOrProperties: function(command, argument){
- // summary:
- // This function handles appplying text color as best it is
- // able to do so when the selection is collapsed, making the
- // behavior cross-browser consistent. It also handles the name
- // and size for IE.
- // command:
- // The command.
- // argument:
- // Any additional arguments.
- // tags:
- // private
- var selection = rangeapi.getSelection(this.window);
- var doc = this.document;
- var rs, ret, range, txt, startNode, endNode, breaker, sNode;
- argument = argument || null;
- if(command && selection && selection.isCollapsed){
- if(selection.rangeCount){
- range = selection.getRangeAt(0);
- rs = range.startContainer;
- if(rs && rs.nodeType === 3){
- // Text node, we have to split it.
- win.withGlobal(this.window, lang.hitch(this, function(){
- var offset = range.startOffset;
- if(rs.length < offset){
- //We are not splitting the right node, try to locate the correct one
- ret = this._adjustNodeAndOffset(rs, offset);
- rs = ret.node;
- offset = ret.offset;
- }
- txt = rs.nodeValue;
- startNode = doc.createTextNode(txt.substring(0, offset));
- var endText = txt.substring(offset);
- if(endText !== ""){
- endNode = doc.createTextNode(txt.substring(offset));
- }
- // Create a space, we'll select and bold it, so
- // the whole word doesn't get bolded
- breaker = domConstruct.create("span");
- sNode = doc.createTextNode(".");
- breaker.appendChild(sNode);
- // Create a junk node to avoid it trying to stlye the breaker.
- // This will get destroyed later.
- var extraSpan = domConstruct.create("span");
- breaker.appendChild(extraSpan);
- if(startNode.length){
- domConstruct.place(startNode, rs, "after");
- }else{
- startNode = rs;
- }
- domConstruct.place(breaker, startNode, "after");
- if(endNode){
- domConstruct.place(endNode, breaker, "after");
- }
- domConstruct.destroy(rs);
- var newrange = rangeapi.create();
- newrange.setStart(sNode, 0);
- newrange.setEnd(sNode, sNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- if(has("webkit")){
- // WebKit is frustrating with positioning the cursor.
- // It stinks to have a selected space, but there really
- // isn't much choice here.
- var style = "color";
- if(command === "hilitecolor" || command === "backcolor"){
- style = "backgroundColor";
- }
- domStyle.set(breaker, style, argument);
- selectionapi.remove();
- domConstruct.destroy(extraSpan);
- breaker.innerHTML = " "; //
- selectionapi.selectElement(breaker);
- this.focus();
- }else{
- this.execCommand(command, argument);
- domConstruct.place(breaker.firstChild, breaker, "before");
- domConstruct.destroy(breaker);
- newrange.setStart(sNode, 0);
- newrange.setEnd(sNode, sNode.length);
- selection.removeAllRanges();
- selection.addRange(newrange);
- selectionapi.collapse(false);
- sNode.parentNode.removeChild(sNode);
- }
- }));
- return true;
- }
- }
- }
- return false;
- },
-
- _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){
- // summary:
- // In the case there are multiple text nodes in a row the offset may not be within the node.
- // If the offset is larger than the node length, it will attempt to find
- // the next text sibling until it locates the text node in which the offset refers to
- // node:
- // The node to check.
- // offset:
- // The position to find within the text node
- // tags:
- // private.
- while(node.length < offset && node.nextSibling && node.nextSibling.nodeType === 3){
- //Adjust the offset and node in the case of multiple text nodes in a row
- offset = offset - node.length;
- node = node.nextSibling;
- }
- return {"node": node, "offset": offset};
- },
-
- _tagNamesForCommand: function(command){
- // summary:
- // Function to return the tab names that are associated
- // with a particular style.
- // command: String
- // The command to return tags for.
- // tags:
- // private
- if(command === "bold"){
- return ["b", "strong"];
- }else if(command === "italic"){
- return ["i","em"];
- }else if(command === "strikethrough"){
- return ["s", "strike"];
- }else if(command === "superscript"){
- return ["sup"];
- }else if(command === "subscript"){
- return ["sub"];
- }else if(command === "underline"){
- return ["u"];
- }
- return [];
- },
- _stripBreakerNodes: function(node){
- // summary:
- // Function for stripping out the breaker spans inserted by the formatting command.
- // Registered as a filter for IE, handles the breaker spans needed to fix up
- // How bold/italic/etc, work when selection is collapsed (single cursor).
- win.withGlobal(this.window, lang.hitch(this, function(){
- var breakers = query(".ieFormatBreakerSpan", node);
- var i;
- for(i = 0; i < breakers.length; i++){
- var b = breakers[i];
- while(b.firstChild){
- domConstruct.place(b.firstChild, b, "before");
- }
- domConstruct.destroy(b);
- }
- }));
- return node;
- }
- });
- return BuxRichText;
- });
- },
- 'dijit/_editor/plugins/TextColor':function(){
- define("dijit/_editor/plugins/TextColor", [
- "require",
- "dojo/colors", // colors.fromRgb
- "dojo/_base/declare", // declare
- "dojo/_base/lang",
- "../_Plugin",
- "../../form/DropDownButton"
- ], function(require, colors, declare, lang, _Plugin, DropDownButton){
- /*=====
- var _Plugin = dijit._editor._Plugin;
- =====*/
- // module:
- // dijit/_editor/plugins/TextColor
- // summary:
- // This plugin provides dropdown color pickers for setting text color and background color
- var TextColor = declare("dijit._editor.plugins.TextColor", _Plugin, {
- // summary:
- // This plugin provides dropdown color pickers for setting text color and background color
- //
- // description:
- // The commands provided by this plugin are:
- // * foreColor - sets the text color
- // * hiliteColor - sets the background color
- // Override _Plugin.buttonClass to use DropDownButton (with ColorPalette) to control this plugin
- buttonClass: DropDownButton,
- // useDefaultCommand: Boolean
- // False as we do not use the default editor command/click behavior.
- useDefaultCommand: false,
- _initButton: function(){
- this.inherited(arguments);
- // Setup to lazy load ColorPalette first time the button is clicked
- var self = this;
- this.button.loadDropDown = function(callback){
- require(["../../ColorPalette"], lang.hitch(this, function(ColorPalette){
- this.dropDown = new ColorPalette({
- value: self.value,
- onChange: function(color){
- self.editor.execCommand(self.command, color);
- }
- });
- callback();
- }));
- };
- },
- updateState: function(){
- // summary:
- // Overrides _Plugin.updateState(). This updates the ColorPalette
- // to show the color of the currently selected text.
- // tags:
- // protected
- var _e = this.editor;
- var _c = this.command;
- if(!_e || !_e.isLoaded || !_c.length){
- return;
- }
- if(this.button){
- var disabled = this.get("disabled");
- this.button.set("disabled", disabled);
- if(disabled){ return; }
- var value;
- try{
- value = _e.queryCommandValue(_c)|| "";
- }catch(e){
- //Firefox may throw error above if the editor is just loaded, ignore it
- value = "";
- }
- }
- if(value == ""){
- value = "#000000";
- }
- if(value == "transparent"){
- value = "#ffffff";
- }
- if(typeof value == "string"){
- //if RGB value, convert to hex value
- if(value.indexOf("rgb")> -1){
- value = colors.fromRgb(value).toHex();
- }
- }else{ //it's an integer(IE returns an MS access #)
- value =((value & 0x0000ff)<< 16)|(value & 0x00ff00)|((value & 0xff0000)>>> 16);
- value = value.toString(16);
- value = "#000000".slice(0, 7 - value.length)+ value;
- }
- this.value = value;
- var dropDown = this.button.dropDown;
- if(dropDown && value !== dropDown.get('value')){
- dropDown.set('value', value, false);
- }
- }
- });
- // Register this plugin.
- _Plugin.registry["foreColor"] = function(){
- return new TextColor({command: "foreColor"});
- };
- _Plugin.registry["hiliteColor"] = function(){
- return new TextColor({command: "hiliteColor"});
- };
- return TextColor;
- });
- },
- 'dijit/_editor/selection':function(){
- define("dijit/_editor/selection", [
- "dojo/dom", // dom.byId
- "dojo/_base/lang",
- "dojo/_base/sniff", // has("ie") has("opera")
- "dojo/_base/window", // win.body win.doc win.doc.createElement win.doc.selection win.doc.selection.createRange win.doc.selection.type.toLowerCase win.global win.global.getSelection
- ".." // for exporting symbols to dijit._editor.selection (TODO: remove in 2.0)
- ], function(dom, lang, has, win, dijit){
- // module:
- // dijit/_editor/selection
- // summary:
- // Text selection API
- lang.getObject("_editor.selection", true, dijit);
- // FIXME:
- // all of these methods branch internally for IE. This is probably
- // sub-optimal in terms of runtime performance. We should investigate the
- // size difference for differentiating at definition time.
- lang.mixin(dijit._editor.selection, {
- getType: function(){
- // summary:
- // Get the selection type (like win.doc.select.type in IE).
- if(!win.doc.getSelection){
- // IE6-8
- return win.doc.selection.type.toLowerCase();
- }else{
- // W3C
- var stype = "text";
- // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...).
- var oSel;
- try{
- oSel = win.global.getSelection();
- }catch(e){ /*squelch*/ }
- if(oSel && oSel.rangeCount == 1){
- var oRange = oSel.getRangeAt(0);
- if( (oRange.startContainer == oRange.endContainer) &&
- ((oRange.endOffset - oRange.startOffset) == 1) &&
- (oRange.startContainer.nodeType != 3 /* text node*/)
- ){
- stype = "control";
- }
- }
- return stype; //String
- }
- },
- getSelectedText: function(){
- // summary:
- // Return the text (no html tags) included in the current selection or null if no text is selected
- if(!win.doc.getSelection){
- // IE6-8
- if(dijit._editor.selection.getType() == 'control'){
- return null;
- }
- return win.doc.selection.createRange().text;
- }else{
- // W3C
- var selection = win.global.getSelection();
- if(selection){
- return selection.toString(); //String
- }
- }
- return '';
- },
- getSelectedHtml: function(){
- // summary:
- // Return the html text of the current selection or null if unavailable
- if(!win.doc.getSelection){
- // IE6-8
- if(dijit._editor.selection.getType() == 'control'){
- return null;
- }
- return win.doc.selection.createRange().htmlText;
- }else{
- // W3C
- var selection = win.global.getSelection();
- if(selection && selection.rangeCount){
- var i;
- var html = "";
- for(i = 0; i < selection.rangeCount; i++){
- //Handle selections spanning ranges, such as Opera
- var frag = selection.getRangeAt(i).cloneContents();
- var div = win.doc.createElement("div");
- div.appendChild(frag);
- html += div.innerHTML;
- }
- return html; //String
- }
- return null;
- }
- },
- getSelectedElement: function(){
- // summary:
- // Retrieves the selected element (if any), just in the case that
- // a single element (object like and image or a table) is
- // selected.
- if(dijit._editor.selection.getType() == "control"){
- if(!win.doc.getSelection){
- // IE6-8
- var range = win.doc.selection.createRange();
- if(range && range.item){
- return win.doc.selection.createRange().item(0);
- }
- }else{
- // W3C
- var selection = win.global.getSelection();
- return selection.anchorNode.childNodes[ selection.anchorOffset ];
- }
- }
- return null;
- },
- getParentElement: function(){
- // summary:
- // Get the parent element of the current selection
- if(dijit._editor.selection.getType() == "control"){
- var p = this.getSelectedElement();
- if(p){ return p.parentNode; }
- }else{
- if(!win.doc.getSelection){
- // IE6-8
- var r = win.doc.selection.createRange();
- r.collapse(true);
- return r.parentElement();
- }else{
- // W3C
- var selection = win.global.getSelection();
- if(selection){
- var node = selection.anchorNode;
- while(node && (node.nodeType != 1)){ // not an element
- node = node.parentNode;
- }
- return node;
- }
- }
- }
- return null;
- },
- hasAncestorElement: function(/*String*/tagName /* ... */){
- // summary:
- // Check whether current selection has a parent element which is
- // of type tagName (or one of the other specified tagName)
- // tagName: String
- // The tag name to determine if it has an ancestor of.
- return this.getAncestorElement.apply(this, arguments) != null; //Boolean
- },
- getAncestorElement: function(/*String*/tagName /* ... */){
- // summary:
- // Return the parent element of the current selection which is of
- // type tagName (or one of the other specified tagName)
- // tagName: String
- // The tag name to determine if it has an ancestor of.
- var node = this.getSelectedElement() || this.getParentElement();
- return this.getParentOfType(node, arguments); //DOMNode
- },
- isTag: function(/*DomNode*/ node, /*String[]*/ tags){
- // summary:
- // Function to determine if a node is one of an array of tags.
- // node:
- // The node to inspect.
- // tags:
- // An array of tag name strings to check to see if the node matches.
- if(node && node.tagName){
- var _nlc = node.tagName.toLowerCase();
- for(var i=0; i<tags.length; i++){
- var _tlc = String(tags[i]).toLowerCase();
- if(_nlc == _tlc){
- return _tlc; // String
- }
- }
- }
- return "";
- },
- getParentOfType: function(/*DomNode*/ node, /*String[]*/ tags){
- // summary:
- // Function to locate a parent node that matches one of a set of tags
- // node:
- // The node to inspect.
- // tags:
- // An array of tag name strings to check to see if the node matches.
- while(node){
- if(this.isTag(node, tags).length){
- return node; // DOMNode
- }
- node = node.parentNode;
- }
- return null;
- },
- collapse: function(/*Boolean*/beginning){
- // summary:
- // Function to collapse (clear), the current selection
- // beginning: Boolean
- // Boolean to indicate whether to collapse the cursor to the beginning of the selection or end.
- if(window.getSelection){
- var selection = win.global.getSelection();
- if(selection.removeAllRanges){ // Mozilla
- if(beginning){
- selection.collapseToStart();
- }else{
- selection.collapseToEnd();
- }
- }else{ // Safari
- // pulled from WebCore/ecma/kjs_window.cpp, line 2536
- selection.collapse(beginning);
- }
- }else if(has("ie")){ // IE
- var range = win.doc.selection.createRange();
- range.collapse(beginning);
- range.select();
- }
- },
- remove: function(){
- // summary:
- // Function to delete the currently selected content from the document.
- var sel = win.doc.selection;
- if(!win.doc.getSelection){
- // IE6-8
- if(sel.type.toLowerCase() != "none"){
- sel.clear();
- }
- return sel; //Selection
- }else{
- // W3C
- sel = win.global.getSelection();
- sel.deleteFromDocument();
- return sel; //Selection
- }
- },
- selectElementChildren: function(/*DomNode*/element,/*Boolean?*/nochangefocus){
- // summary:
- // clear previous selection and select the content of the node
- // (excluding the node itself)
- // element: DOMNode
- // The element you wish to select the children content of.
- // nochangefocus: Boolean
- // Boolean to indicate if the foxus should change or not.
- var global = win.global;
- var doc = win.doc;
- var range;
- element = dom.byId(element);
- if(doc.selection && !doc.getSelection && win.body().createTextRange){
- // IE6-8
- range = element.ownerDocument.body.createTextRange();
- range.moveToElementText(element);
- if(!nochangefocus){
- try{
- range.select(); // IE throws an exception here if the widget is hidden. See #5439
- }catch(e){ /* squelch */}
- }
- }else if(global.getSelection){
- // W3C
- var selection = win.global.getSelection();
- if(has("opera")){
- //Opera's selectAllChildren doesn't seem to work right
- //against <body> nodes and possibly others ... so
- //we use the W3C range API
- if(selection.rangeCount){
- range = selection.getRangeAt(0);
- }else{
- range = doc.createRange();
- }
- range.setStart(element, 0);
- range.setEnd(element,(element.nodeType == 3)?element.length:element.childNodes.length);
- selection.addRange(range);
- }else{
- selection.selectAllChildren(element);
- }
- }
- },
- selectElement: function(/*DomNode*/element,/*Boolean?*/nochangefocus){
- // summary:
- // clear previous selection and select element (including all its children)
- // element: DOMNode
- // The element to select.
- // nochangefocus: Boolean
- // Boolean indicating if the focus should be changed. IE only.
- var range;
- var doc = win.doc;
- var global = win.global;
- element = dom.byId(element);
- if(!doc.getSelection && win.body().createTextRange){
- // IE6-8
- try{
- var tg = element.tagName ? element.tagName.toLowerCase() : "";
- if(tg === "img" || tg === "table"){
- range = win.body().createControlRange();
- }else{
- range = win.body().createRange();
- }
- range.addElement(element);
- if(!nochangefocus){
- range.select();
- }
- }catch(e){
- this.selectElementChildren(element,nochangefocus);
- }
- }else if(global.getSelection){
- // W3C
- var selection = global.getSelection();
- range = doc.createRange();
- if(selection.removeAllRanges){ // Mozilla
- // FIXME: does this work on Safari?
- if(has("opera")){
- //Opera works if you use the current range on
- //the selection if present.
- if(selection.getRangeAt(0)){
- range = selection.getRangeAt(0);
- }
- }
- range.selectNode(element);
- selection.removeAllRanges();
- selection.addRange(range);
- }
- }
- },
- inSelection: function(node){
- // summary:
- // This function determines if 'node' is
- // in the current selection.
- // tags:
- // public
- if(node){
- var newRange;
- var doc = win.doc;
- var range;
- if(win.global.getSelection){
- //WC3
- var sel = win.global.getSelection();
- if(sel && sel.rangeCount > 0){
- range = sel.getRangeAt(0);
- }
- if(range && range.compareBoundaryPoints && doc.createRange){
- try{
- newRange = doc.createRange();
- newRange.setStart(node, 0);
- if(range.compareBoundaryPoints(range.START_TO_END, newRange) === 1){
- return true;
- }
- }catch(e){ /* squelch */}
- }
- }else if(doc.selection){
- // Probably IE, so we can't use the range object as the pseudo
- // range doesn't implement the boundry checking, we have to
- // use IE specific crud.
- range = doc.selection.createRange();
- try{
- newRange = node.ownerDocument.body.createControlRange();
- if(newRange){
- newRange.addElement(node);
- }
- }catch(e1){
- try{
- newRange = node.ownerDocument.body.createTextRange();
- newRange.moveToElementText(node);
- }catch(e2){/* squelch */}
- }
- if(range && newRange){
- // We can finally compare similar to W3C
- if(range.compareEndPoints("EndToStart", newRange) === 1){
- return true;
- }
- }
- }
- }
- return false; // boolean
- }
- });
- return dijit._editor.selection;
- });
- },
- 'dijit/_editor/range':function(){
- define("dijit/_editor/range", [
- "dojo/_base/array", // array.every
- "dojo/_base/declare", // declare
- "dojo/_base/lang", // lang.isArray
- "dojo/_base/window", // win.global
- ".." // for exporting symbols to dijit, TODO: remove in 2.0
- ], function(array, declare, lang, win, dijit){
- // module:
- // dijit/_editor/range
- // summary:
- // W3C range API
- dijit.range={};
- dijit.range.getIndex = function(/*DomNode*/node, /*DomNode*/parent){
- // dojo.profile.start("dijit.range.getIndex");
- var ret = [], retR = [];
- var onode = node;
- var pnode, n;
- while(node != parent){
- var i = 0;
- pnode = node.parentNode;
- while((n = pnode.childNodes[i++])){
- if(n === node){
- --i;
- break;
- }
- }
- //if(i>=pnode.childNodes.length){
- //dojo.debug("Error finding index of a node in dijit.range.getIndex");
- //}
- ret.unshift(i);
- retR.unshift(i - pnode.childNodes.length);
- node = pnode;
- }
- //normalized() can not be called so often to prevent
- //invalidating selection/range, so we have to detect
- //here that any text nodes in a row
- if(ret.length > 0 && onode.nodeType == 3){
- n = onode.previousSibling;
- while(n && n.nodeType == 3){
- ret[ret.length - 1]--;
- n = n.previousSibling;
- }
- n = onode.nextSibling;
- while(n && n.nodeType == 3){
- retR[retR.length - 1]++;
- n = n.nextSibling;
- }
- }
- // dojo.profile.end("dijit.range.getIndex");
- return {o: ret, r:retR};
- };
- dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
- if(!lang.isArray(index) || index.length == 0){
- return parent;
- }
- var node = parent;
- // if(!node)debugger
- array.every(index, function(i){
- if(i >= 0 && i < node.childNodes.length){
- node = node.childNodes[i];
- }else{
- node = null;
- //console.debug('Error: can not find node with index',index,'under parent node',parent );
- return false; //terminate array.every
- }
- return true; //carry on the every loop
- });
- return node;
- };
- dijit.range.getCommonAncestor = function(n1, n2, root){
- root = root || n1.ownerDocument.body;
- var getAncestors = function(n){
- var as = [];
- while(n){
- as.unshift(n);
- if(n !== root){
- n = n.parentNode;
- }else{
- break;
- }
- }
- return as;
- };
- var n1as = getAncestors(n1);
- var n2as = getAncestors(n2);
- var m = Math.min(n1as.length, n2as.length);
- var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
- for(var i = 1; i < m; i++){
- if(n1as[i] === n2as[i]){
- com = n1as[i]
- }else{
- break;
- }
- }
- return com;
- };
- dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
- root = root || node.ownerDocument.body;
- while(node && node !== root){
- var name = node.nodeName.toUpperCase();
- if(regex.test(name)){
- return node;
- }
- node = node.parentNode;
- }
- return null;
- };
- dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
- dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
- root = root || node.ownerDocument.body;
- regex = regex || dijit.range.BlockTagNames;
- var block = null, blockContainer;
- while(node && node !== root){
- var name = node.nodeName.toUpperCase();
- if(!block && regex.test(name)){
- block = node;
- }
- if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
- blockContainer = node;
- }
- node = node.parentNode;
- }
- return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
- };
- dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
- var atBeginning = false;
- var offsetAtBeginning = (offset == 0);
- if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space
- if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0, offset))){
- offsetAtBeginning = true;
- }
- }
- if(offsetAtBeginning){
- var cnode = node;
- atBeginning = true;
- while(cnode && cnode !== container){
- if(cnode.previousSibling){
- atBeginning = false;
- break;
- }
- cnode = cnode.parentNode;
- }
- }
- return atBeginning;
- };
- dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
- var atEnd = false;
- var offsetAtEnd = (offset == (node.length || node.childNodes.length));
- if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space
- if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){
- offsetAtEnd = true;
- }
- }
- if(offsetAtEnd){
- var cnode = node;
- atEnd = true;
- while(cnode && cnode !== container){
- if(cnode.nextSibling){
- atEnd = false;
- break;
- }
- cnode = cnode.parentNode;
- }
- }
- return atEnd;
- };
- dijit.range.adjacentNoneTextNode = function(startnode, next){
- var node = startnode;
- var len = (0 - startnode.length) || 0;
- var prop = next ? 'nextSibling' : 'previousSibling';
- while(node){
- if(node.nodeType != 3){
- break;
- }
- len += node.length;
- node = node[prop];
- }
- return [node,len];
- };
- dijit.range._w3c = Boolean(window['getSelection']);
- dijit.range.create = function(/*Window?*/window){
- if(dijit.range._w3c){
- return (window || win.global).document.createRange();
- }else{//IE
- return new dijit.range.W3CRange;
- }
- };
- dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){
- if(dijit.range._w3c){
- return win.getSelection();
- }else{//IE
- var s = new dijit.range.ie.selection(win);
- if(!ignoreUpdate){
- s._getCurrentSelection();
- }
- return s;
- }
- };
- if(!dijit.range._w3c){
- dijit.range.ie = {
- cachedSelection: {},
- selection: function(win){
- this._ranges = [];
- this.addRange = function(r, /*boolean*/internal){
- this._ranges.push(r);
- if(!internal){
- r._select();
- }
- this.rangeCount = this._ranges.length;
- };
- this.removeAllRanges = function(){
- //don't detach, the range may be used later
- // for(var i=0;i<this._ranges.length;i++){
- // this._ranges[i].detach();
- // }
- this._ranges = [];
- this.rangeCount = 0;
- };
- var _initCurrentRange = function(){
- var r = win.document.selection.createRange();
- var type = win.document.selection.type.toUpperCase();
- if(type == "CONTROL"){
- //TODO: multiple range selection(?)
- return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
- }else{
- return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
- }
- };
- this.getRangeAt = function(i){
- return this._ranges[i];
- };
- this._getCurrentSelection = function(){
- this.removeAllRanges();
- var r = _initCurrentRange();
- if(r){
- this.addRange(r, true);
- this.isCollapsed = r.collapsed;
- }else{
- this.isCollapsed = true;
- }
- };
- },
- decomposeControlRange: function(range){
- var firstnode = range.item(0), lastnode = range.item(range.length - 1);
- var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
- var startOffset = dijit.range.getIndex(firstnode, startContainer).o[0];
- var endOffset = dijit.range.getIndex(lastnode, endContainer).o[0] + 1;
- return [startContainer, startOffset,endContainer, endOffset];
- },
- getEndPoint: function(range, end){
- var atmrange = range.duplicate();
- atmrange.collapse(!end);
- var cmpstr = 'EndTo' + (end ? 'End' : 'Start');
- var parentNode = atmrange.parentElement();
- var startnode, startOffset, lastNode;
- if(parentNode.childNodes.length > 0){
- array.every(parentNode.childNodes, function(node, i){
- var calOffset;
- if(node.nodeType != 3){
- atmrange.moveToElementText(node);
- if(atmrange.compareEndPoints(cmpstr, range) > 0){
- //startnode = node.previousSibling;
- if(lastNode && lastNode.nodeType == 3){
- //where shall we put the start? in the text node or after?
- startnode = lastNode;
- calOffset = true;
- }else{
- startnode = parentNode;
- startOffset = i;
- return false;
- }
- }else{
- if(i == parentNode.childNodes.length - 1){
- startnode = parentNode;
- startOffset = parentNode.childNodes.length;
- return false;
- }
- }
- }else{
- if(i == parentNode.childNodes.length - 1){//at the end of this node
- startnode = node;
- calOffset = true;
- }
- }
- // try{
- if(calOffset && startnode){
- var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
- if(prevnode){
- startnode = prevnode.nextSibling;
- }else{
- startnode = parentNode.firstChild; //firstChild must be a text node
- }
- var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
- prevnode = prevnodeobj[0];
- var lenoffset = prevnodeobj[1];
- if(prevnode){
- atmrange.moveToElementText(prevnode);
- atmrange.collapse(false);
- }else{
- atmrange.moveToElementText(parentNode);
- }
- atmrange.setEndPoint(cmpstr, range);
- startOffset = atmrange.text.length - lenoffset;
- return false;
- }
- // }catch(e){ debugger }
- lastNode = node;
- return true;
- });
- }else{
- startnode = parentNode;
- startOffset = 0;
- }
- //if at the end of startnode and we are dealing with start container, then
- //move the startnode to nextSibling if it is a text node
- //TODO: do this for end container?
- if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){
- var nextnode = startnode.nextSibling;
- if(nextnode && nextnode.nodeType == 3){
- startnode = nextnode;
- startOffset = 0;
- }
- }
- return [startnode, startOffset];
- },
- setEndPoint: function(range, container, offset){
- //text node
- var atmrange = range.duplicate(), node, len;
- if(container.nodeType != 3){ //normal node
- if(offset > 0){
- node = container.childNodes[offset - 1];
- if(node){
- if(node.nodeType == 3){
- container = node;
- offset = node.length;
- //pass through
- }else{
- if(node.nextSibling && node.nextSibling.nodeType == 3){
- container = node.nextSibling;
- offset = 0;
- //pass through
- }else{
- atmrange.moveToElementText(node.nextSibling ? node : container);
- var parent = node.parentNode;
- var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling);
- atmrange.collapse(false);
- parent.removeChild(tempNode);
- }
- }
- }
- }else{
- atmrange.moveToElementText(container);
- atmrange.collapse(true);
- }
- }
- if(container.nodeType == 3){
- var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
- var prevnode = prevnodeobj[0];
- len = prevnodeobj[1];
- if(prevnode){
- atmrange.moveToElementText(prevnode);
- atmrange.collapse(false);
- //if contentEditable is not inherit, the above collapse won't make the end point
- //in the correctly position: it always has a -1 offset, so compensate it
- if(prevnode.contentEditable != 'inherit'){
- len++;
- }
- }else{
- atmrange.moveToElementText(container.parentNode);
- atmrange.collapse(true);
- }
- offset += len;
- if(offset > 0){
- if(atmrange.move('character', offset) != offset){
- console.error('Error when moving!');
- }
- }
- }
- return atmrange;
- },
- decomposeTextRange: function(range){
- var tmpary = dijit.range.ie.getEndPoint(range);
- var startContainer = tmpary[0], startOffset = tmpary[1];
- var endContainer = tmpary[0], endOffset = tmpary[1];
- if(range.htmlText.length){
- if(range.htmlText == range.text){ //in the same text node
- endOffset = startOffset + range.text.length;
- }else{
- tmpary = dijit.range.ie.getEndPoint(range, true);
- endContainer = tmpary[0],endOffset = tmpary[1];
- // if(startContainer.tagName == "BODY"){
- // startContainer = startContainer.firstChild;
- // }
- }
- }
- return [startContainer, startOffset, endContainer, endOffset];
- },
- setRange: function(range, startContainer, startOffset, endContainer, endOffset, collapsed){
- var start = dijit.range.ie.setEndPoint(range, startContainer, startOffset);
- range.setEndPoint('StartToStart', start);
- if(!collapsed){
- var end = dijit.range.ie.setEndPoint(range, endContainer, endOffset);
- }
- range.setEndPoint('EndToEnd', end || start);
- return range;
- }
- };
- declare("dijit.range.W3CRange",null, {
- constructor: function(){
- if(arguments.length>0){
- this.setStart(arguments[0][0],arguments[0][1]);
- this.setEnd(arguments[0][2],arguments[0][3]);
- }else{
- this.commonAncestorContainer = null;
- this.startContainer = null;
- this.startOffset = 0;
- this.endContainer = null;
- this.endOffset = 0;
- this.collapsed = true;
- }
- },
- _updateInternal: function(){
- if(this.startContainer !== this.endContainer){
- this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer);
- }else{
- this.commonAncestorContainer = this.startContainer;
- }
- this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
- },
- setStart: function(node, offset){
- offset=parseInt(offset);
- if(this.startContainer === node && this.startOffset == offset){
- return;
- }
- delete this._cachedBookmark;
- this.startContainer = node;
- this.startOffset = offset;
- if(!this.endContainer){
- this.setEnd(node, offset);
- }else{
- this._updateInternal();
- }
- },
- setEnd: function(node, offset){
- offset=parseInt(offset);
- if(this.endContainer === node && this.endOffset == offset){
- return;
- }
- delete this._cachedBookmark;
- this.endContainer = node;
- this.endOffset = offset;
- if(!this.startContainer){
- this.setStart(node, offset);
- }else{
- this._updateInternal();
- }
- },
- setStartAfter: function(node, offset){
- this._setPoint('setStart', node, offset, 1);
- },
- setStartBefore: function(node, offset){
- this._setPoint('setStart', node, offset, 0);
- },
- setEndAfter: function(node, offset){
- this._setPoint('setEnd', node, offset, 1);
- },
- setEndBefore: function(node, offset){
- this._setPoint('setEnd', node, offset, 0);
- },
- _setPoint: function(what, node, offset, ext){
- var index = dijit.range.getIndex(node, node.parentNode).o;
- this[what](node.parentNode, index.pop()+ext);
- },
- _getIERange: function(){
- var r = (this._body || this.endContainer.ownerDocument.body).createTextRange();
- dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed);
- return r;
- },
- getBookmark: function(){
- this._getIERange();
- return this._cachedBookmark;
- },
- _select: function(){
- var r = this._getIERange();
- r.select();
- },
- deleteContents: function(){
- var s = this.startContainer, r = this._getIERange();
- if(s.nodeType === 3 && !this.startOffset){
- //if the range starts at the beginning of a
- //text node, move it to before the textnode
- //to make sure the range is still valid
- //after deleteContents() finishes
- this.setStartBefore(s);
- }
- r.pasteHTML('');
- this.endContainer = this.startContainer;
- this.endOffset = this.startOffset;
- this.collapsed = true;
- },
- cloneRange: function(){
- var r = new dijit.range.W3CRange([this.startContainer,this.startOffset,
- this.endContainer,this.endOffset]);
- r._body = this._body;
- return r;
- },
- detach: function(){
- this._body = null;
- this.commonAncestorContainer = null;
- this.startContainer = null;
- this.startOffset = 0;
- this.endContainer = null;
- this.endOffset = 0;
- this.collapsed = true;
- }
- });
- } //if(!dijit.range._w3c)
- return dijit.range;
- });
- },
- '*now':function(r){r(['dojo/i18n!*preload*dojo/nls/buxTextEditor*["ar","az","bg","ca","cs","da","de","de-de","el","en","en-ca","en-gb","en-us","es","es-es","fi","fi-fi","fr","fr-ca","fr-fr","he","he-il","hr","hu","it","it-it","ja","ja-jp","kk","ko","ko-kr","nl","nl-nl","nb","no","pl","pt","pt-br","pt-pt","ro","ru","sk","sl","sv","th","tr","zh","zh-tw","zh-cn","ROOT"]']);}
- }});
- define("dojo/buxTextEditor", [], 1);
|