buxTextEditor.js.uncompressed.js 207 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632
  1. require({cache:{
  2. 'dijit/_editor/plugins/BuxEnterKeyHandling':function(){
  3. define("dijit/_editor/plugins/BuxEnterKeyHandling", [
  4. "dojo/_base/declare", // declare
  5. "dojo/dom-construct", // domConstruct.destroy domConstruct.place
  6. "dojo/_base/event", // event.stop
  7. "dojo/keys", // keys.ENTER
  8. "dojo/_base/lang",
  9. "dojo/_base/sniff", // has("ie") has("mozilla") has("webkit")
  10. "dojo/_base/window", // win.global win.withGlobal
  11. "dojo/window", // winUtils.scrollIntoView
  12. "../_Plugin",
  13. "../BuxRichText",
  14. "../range",
  15. "../selection"
  16. ], function(declare, domConstruct, event, keys, lang, has, win, winUtils, _Plugin, BuxRichText, rangeapi, selectionapi){
  17. /*=====
  18. var _Plugin = dijit._editor._Plugin;
  19. =====*/
  20. // module:
  21. // dijit/_editor/plugins/EnterKeyHandling
  22. // summary:
  23. // This plugin tries to make all browsers behave consistently with regard to
  24. // how ENTER behaves in the editor window. It traps the ENTER key and alters
  25. // the way DOM is constructed in certain cases to try to commonize the generated
  26. // DOM and behaviors across browsers.
  27. return declare("dijit._editor.plugins.EnterKeyHandling", _Plugin, {
  28. // summary:
  29. // This plugin tries to make all browsers behave consistently with regard to
  30. // how ENTER behaves in the editor window. It traps the ENTER key and alters
  31. // the way DOM is constructed in certain cases to try to commonize the generated
  32. // DOM and behaviors across browsers.
  33. //
  34. // description:
  35. // This plugin has three modes:
  36. //
  37. // * blockNodeForEnter=BR
  38. // * blockNodeForEnter=DIV
  39. // * blockNodeForEnter=P
  40. //
  41. // In blockNodeForEnter=P, the ENTER key starts a new
  42. // paragraph, and shift-ENTER starts a new line in the current paragraph.
  43. // For example, the input:
  44. //
  45. // | first paragraph <shift-ENTER>
  46. // | second line of first paragraph <ENTER>
  47. // | second paragraph
  48. //
  49. // will generate:
  50. //
  51. // | <p>
  52. // | first paragraph
  53. // | <br/>
  54. // | second line of first paragraph
  55. // | </p>
  56. // | <p>
  57. // | second paragraph
  58. // | </p>
  59. //
  60. // In BR and DIV mode, the ENTER key conceptually goes to a new line in the
  61. // current paragraph, and users conceptually create a new paragraph by pressing ENTER twice.
  62. // For example, if the user enters text into an editor like this:
  63. //
  64. // | one <ENTER>
  65. // | two <ENTER>
  66. // | three <ENTER>
  67. // | <ENTER>
  68. // | four <ENTER>
  69. // | five <ENTER>
  70. // | six <ENTER>
  71. //
  72. // It will appear on the screen as two 'paragraphs' of three lines each. Markupwise, this generates:
  73. //
  74. // BR:
  75. // | one<br/>
  76. // | two<br/>
  77. // | three<br/>
  78. // | <br/>
  79. // | four<br/>
  80. // | five<br/>
  81. // | six<br/>
  82. //
  83. // DIV:
  84. // | <div>one</div>
  85. // | <div>two</div>
  86. // | <div>three</div>
  87. // | <div>&nbsp;</div>
  88. // | <div>four</div>
  89. // | <div>five</div>
  90. // | <div>six</div>
  91. // blockNodeForEnter: String
  92. // This property decides the behavior of Enter key. It can be either P,
  93. // DIV, BR, or empty (which means disable this feature). Anything else
  94. // will trigger errors. The default is 'BR'
  95. //
  96. // See class description for more details.
  97. blockNodeForEnter: 'BR',
  98. constructor: function(args){
  99. if(args){
  100. if("blockNodeForEnter" in args){
  101. args.blockNodeForEnter = args.blockNodeForEnter.toUpperCase();
  102. }
  103. lang.mixin(this,args);
  104. }
  105. },
  106. setEditor: function(editor){
  107. // Overrides _Plugin.setEditor().
  108. if(this.editor === editor){ return; }
  109. this.editor = editor;
  110. if(this.blockNodeForEnter == 'BR'){
  111. // While Moz has a mode tht mostly works, it's still a little different,
  112. // So, try to just have a common mode and be consistent. Which means
  113. // we need to enable customUndo, if not already enabled.
  114. this.editor.customUndo = true;
  115. editor.onLoadDeferred.then(lang.hitch(this,function(d){
  116. this.connect(editor.document, "onkeypress", function(e){
  117. if(e.charOrCode == keys.ENTER){
  118. // Just do it manually. The handleEnterKey has a shift mode that
  119. // Always acts like <br>, so just use it.
  120. var ne = lang.mixin({},e);
  121. ne.shiftKey = true;
  122. if(!this.handleEnterKey(ne)){
  123. event.stop(e);
  124. }
  125. }
  126. });
  127. if(has("ie") >= 9){
  128. this.connect(editor.document, "onpaste", function(e){
  129. setTimeout(dojo.hitch(this, function(){
  130. // Use the old range/selection code to kick IE 9 into updating
  131. // its range by moving it back, then forward, one 'character'.
  132. var r = this.editor.document.selection.createRange();
  133. r.move('character',-1);
  134. r.select();
  135. r.move('character',1);
  136. r.select();
  137. }),0);
  138. });
  139. }
  140. return d;
  141. }));
  142. }else if(this.blockNodeForEnter){
  143. // add enter key handler
  144. // FIXME: need to port to the new event code!!
  145. var h = lang.hitch(this,this.handleEnterKey);
  146. editor.addKeyHandler(13, 0, 0, h); //enter
  147. editor.addKeyHandler(13, 0, 1, h); //shift+enter
  148. this.connect(this.editor,'onKeyPressed','onKeyPressed');
  149. }
  150. },
  151. onKeyPressed: function(){
  152. // summary:
  153. // Handler for keypress events.
  154. // tags:
  155. // private
  156. if(this._checkListLater){
  157. if(win.withGlobal(this.editor.window, 'isCollapsed', dijit)){
  158. var liparent=win.withGlobal(this.editor.window, 'getAncestorElement', selectionapi, ['LI']);
  159. if(!liparent){
  160. // circulate the undo detection code by calling BuxRichText::execCommand directly
  161. BuxRichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
  162. // set the innerHTML of the new block node
  163. var block = win.withGlobal(this.editor.window, 'getAncestorElement', selectionapi, [this.blockNodeForEnter]);
  164. if(block){
  165. block.innerHTML=this.bogusHtmlContent;
  166. if(has("ie") <= 9){
  167. // move to the start by moving backwards one char
  168. var r = this.editor.document.selection.createRange();
  169. r.move('character',-1);
  170. r.select();
  171. }
  172. }else{
  173. console.error('onKeyPressed: Cannot find the new block node'); // FIXME
  174. }
  175. }else{
  176. if(has("mozilla")){
  177. if(liparent.parentNode.parentNode.nodeName == 'LI'){
  178. liparent=liparent.parentNode.parentNode;
  179. }
  180. }
  181. var fc=liparent.firstChild;
  182. if(fc && fc.nodeType == 1 && (fc.nodeName == 'UL' || fc.nodeName == 'OL')){
  183. liparent.insertBefore(fc.ownerDocument.createTextNode('\xA0'),fc);
  184. var newrange = rangeapi.create(this.editor.window);
  185. newrange.setStart(liparent.firstChild,0);
  186. var selection = rangeapi.getSelection(this.editor.window, true);
  187. selection.removeAllRanges();
  188. selection.addRange(newrange);
  189. }
  190. }
  191. }
  192. this._checkListLater = false;
  193. }
  194. if(this._pressedEnterInBlock){
  195. // the new created is the original current P, so we have previousSibling below
  196. if(this._pressedEnterInBlock.previousSibling){
  197. this.removeTrailingBr(this._pressedEnterInBlock.previousSibling);
  198. }
  199. delete this._pressedEnterInBlock;
  200. }
  201. },
  202. // bogusHtmlContent: [private] String
  203. // HTML to stick into a new empty block
  204. bogusHtmlContent: '&#160;', // &nbsp;
  205. // blockNodes: [private] Regex
  206. // Regex for testing if a given tag is a block level (display:block) tag
  207. blockNodes: /^(?:P|H1|H2|H3|H4|H5|H6|LI)$/,
  208. handleEnterKey: function(e){
  209. // summary:
  210. // Handler for enter key events when blockNodeForEnter is DIV or P.
  211. // description:
  212. // Manually handle enter key event to make the behavior consistent across
  213. // all supported browsers. See class description for details.
  214. // tags:
  215. // private
  216. var selection, range, newrange, startNode, endNode, brNode, doc=this.editor.document,br,rs,txt;
  217. if(e.shiftKey){ // shift+enter always generates <br>
  218. var parent = win.withGlobal(this.editor.window, "getParentElement", selectionapi);
  219. var header = rangeapi.getAncestor(parent,this.blockNodes);
  220. if(header){
  221. if(header.tagName == 'LI'){
  222. return true; // let browser handle
  223. }
  224. selection = rangeapi.getSelection(this.editor.window);
  225. range = selection.getRangeAt(0);
  226. if(!range.collapsed){
  227. range.deleteContents();
  228. selection = rangeapi.getSelection(this.editor.window);
  229. range = selection.getRangeAt(0);
  230. }
  231. if(rangeapi.atBeginningOfContainer(header, range.startContainer, range.startOffset)){
  232. br=doc.createElement('br');
  233. newrange = rangeapi.create(this.editor.window);
  234. header.insertBefore(br,header.firstChild);
  235. newrange.setStartAfter(br);
  236. selection.removeAllRanges();
  237. selection.addRange(newrange);
  238. }else if(rangeapi.atEndOfContainer(header, range.startContainer, range.startOffset)){
  239. newrange = rangeapi.create(this.editor.window);
  240. br=doc.createElement('br');
  241. header.appendChild(br);
  242. header.appendChild(doc.createTextNode('\xA0'));
  243. newrange.setStart(header.lastChild,0);
  244. selection.removeAllRanges();
  245. selection.addRange(newrange);
  246. }else{
  247. rs = range.startContainer;
  248. if(rs && rs.nodeType == 3){
  249. // Text node, we have to split it.
  250. txt = rs.nodeValue;
  251. win.withGlobal(this.editor.window, function(){
  252. startNode = doc.createTextNode(txt.substring(0, range.startOffset));
  253. endNode = doc.createTextNode(txt.substring(range.startOffset));
  254. brNode = doc.createElement("br");
  255. if(endNode.nodeValue == "" && has("webkit")){
  256. endNode = doc.createTextNode('\xA0')
  257. }
  258. domConstruct.place(startNode, rs, "after");
  259. domConstruct.place(brNode, startNode, "after");
  260. domConstruct.place(endNode, brNode, "after");
  261. domConstruct.destroy(rs);
  262. newrange = rangeapi.create();
  263. newrange.setStart(endNode,0);
  264. selection.removeAllRanges();
  265. selection.addRange(newrange);
  266. });
  267. return false;
  268. }
  269. return true; // let browser handle
  270. }
  271. }else{
  272. selection = rangeapi.getSelection(this.editor.window);
  273. if(selection.rangeCount){
  274. range = selection.getRangeAt(0);
  275. if(range && range.startContainer){
  276. if(!range.collapsed){
  277. range.deleteContents();
  278. selection = rangeapi.getSelection(this.editor.window);
  279. range = selection.getRangeAt(0);
  280. }
  281. rs = range.startContainer;
  282. if(rs && rs.nodeType == 3){
  283. // Text node, we have to split it.
  284. win.withGlobal(this.editor.window, lang.hitch(this, function(){
  285. var endEmpty = false;
  286. var offset = range.startOffset;
  287. if(rs.length < offset){
  288. //We are not splitting the right node, try to locate the correct one
  289. ret = this._adjustNodeAndOffset(rs, offset);
  290. rs = ret.node;
  291. offset = ret.offset;
  292. }
  293. txt = rs.nodeValue;
  294. startNode = doc.createTextNode(txt.substring(0, offset));
  295. endNode = doc.createTextNode(txt.substring(offset));
  296. brNode = doc.createElement("br");
  297. if(!endNode.length){
  298. endNode = doc.createTextNode('\xA0');
  299. endEmpty = true;
  300. }
  301. if(startNode.length){
  302. domConstruct.place(startNode, rs, "after");
  303. }else{
  304. startNode = rs;
  305. }
  306. domConstruct.place(brNode, startNode, "after");
  307. domConstruct.place(endNode, brNode, "after");
  308. domConstruct.destroy(rs);
  309. newrange = rangeapi.create();
  310. newrange.setStart(endNode,0);
  311. newrange.setEnd(endNode, endNode.length);
  312. selection.removeAllRanges();
  313. selection.addRange(newrange);
  314. if(endEmpty && !has("webkit")){
  315. selectionapi.remove();
  316. }else{
  317. selectionapi.collapse(true);
  318. }
  319. }));
  320. }else{
  321. var targetNode;
  322. if(range.startOffset >= 0){
  323. targetNode = rs.childNodes[range.startOffset];
  324. }
  325. win.withGlobal(this.editor.window, lang.hitch(this, function(){
  326. var brNode = doc.createElement("br");
  327. var endNode = doc.createTextNode('\xA0');
  328. if(!targetNode){
  329. rs.appendChild(brNode);
  330. rs.appendChild(endNode);
  331. }else{
  332. domConstruct.place(brNode, targetNode, "before");
  333. domConstruct.place(endNode, brNode, "after");
  334. }
  335. newrange = rangeapi.create(win.global);
  336. newrange.setStart(endNode,0);
  337. newrange.setEnd(endNode, endNode.length);
  338. selection.removeAllRanges();
  339. selection.addRange(newrange);
  340. selectionapi.collapse(true);
  341. }));
  342. }
  343. }
  344. }else{
  345. // don't change this: do not call this.execCommand, as that may have other logic in subclass
  346. BuxRichText.prototype.execCommand.call(this.editor, 'inserthtml', '<br>');
  347. }
  348. }
  349. return false;
  350. }
  351. var _letBrowserHandle = true;
  352. // first remove selection
  353. selection = rangeapi.getSelection(this.editor.window);
  354. range = selection.getRangeAt(0);
  355. if(!range.collapsed){
  356. range.deleteContents();
  357. selection = rangeapi.getSelection(this.editor.window);
  358. range = selection.getRangeAt(0);
  359. }
  360. var block = rangeapi.getBlockAncestor(range.endContainer, null, this.editor.editNode);
  361. var blockNode = block.blockNode;
  362. // if this is under a LI or the parent of the blockNode is LI, just let browser to handle it
  363. if((this._checkListLater = (blockNode && (blockNode.nodeName == 'LI' || blockNode.parentNode.nodeName == 'LI')))){
  364. if(has("mozilla")){
  365. // press enter in middle of P may leave a trailing <br/>, let's remove it later
  366. this._pressedEnterInBlock = blockNode;
  367. }
  368. // if this li only contains spaces, set the content to empty so the browser will outdent this item
  369. if(/^(\s|&nbsp;|&#160;|\xA0|<span\b[^>]*\bclass=['"]Apple-style-span['"][^>]*>(\s|&nbsp;|&#160;|\xA0)<\/span>)?(<br>)?$/.test(blockNode.innerHTML)){
  370. // empty LI node
  371. blockNode.innerHTML = '';
  372. if(has("webkit")){ // WebKit tosses the range when innerHTML is reset
  373. newrange = rangeapi.create(this.editor.window);
  374. newrange.setStart(blockNode, 0);
  375. selection.removeAllRanges();
  376. selection.addRange(newrange);
  377. }
  378. this._checkListLater = false; // nothing to check since the browser handles outdent
  379. }
  380. return true;
  381. }
  382. // text node directly under body, let's wrap them in a node
  383. if(!block.blockNode || block.blockNode===this.editor.editNode){
  384. try{
  385. BuxRichText.prototype.execCommand.call(this.editor, 'formatblock',this.blockNodeForEnter);
  386. }catch(e2){ /*squelch FF3 exception bug when editor content is a single BR*/ }
  387. // get the newly created block node
  388. // FIXME
  389. block = {blockNode:win.withGlobal(this.editor.window, "getAncestorElement", selectionapi, [this.blockNodeForEnter]),
  390. blockContainer: this.editor.editNode};
  391. if(block.blockNode){
  392. if(block.blockNode != this.editor.editNode &&
  393. (!(block.blockNode.textContent || block.blockNode.innerHTML).replace(/^\s+|\s+$/g, "").length)){
  394. this.removeTrailingBr(block.blockNode);
  395. return false;
  396. }
  397. }else{ // we shouldn't be here if formatblock worked
  398. block.blockNode = this.editor.editNode;
  399. }
  400. selection = rangeapi.getSelection(this.editor.window);
  401. range = selection.getRangeAt(0);
  402. }
  403. var newblock = doc.createElement(this.blockNodeForEnter);
  404. newblock.innerHTML=this.bogusHtmlContent;
  405. this.removeTrailingBr(block.blockNode);
  406. var endOffset = range.endOffset;
  407. var node = range.endContainer;
  408. if(node.length < endOffset){
  409. //We are not checking the right node, try to locate the correct one
  410. var ret = this._adjustNodeAndOffset(node, endOffset);
  411. node = ret.node;
  412. endOffset = ret.offset;
  413. }
  414. if(rangeapi.atEndOfContainer(block.blockNode, node, endOffset)){
  415. if(block.blockNode === block.blockContainer){
  416. block.blockNode.appendChild(newblock);
  417. }else{
  418. domConstruct.place(newblock, block.blockNode, "after");
  419. }
  420. _letBrowserHandle = false;
  421. // lets move caret to the newly created block
  422. newrange = rangeapi.create(this.editor.window);
  423. newrange.setStart(newblock, 0);
  424. selection.removeAllRanges();
  425. selection.addRange(newrange);
  426. if(this.editor.height){
  427. winUtils.scrollIntoView(newblock);
  428. }
  429. }else if(rangeapi.atBeginningOfContainer(block.blockNode,
  430. range.startContainer, range.startOffset)){
  431. domConstruct.place(newblock, block.blockNode, block.blockNode === block.blockContainer ? "first" : "before");
  432. if(newblock.nextSibling && this.editor.height){
  433. // position input caret - mostly WebKit needs this
  434. newrange = rangeapi.create(this.editor.window);
  435. newrange.setStart(newblock.nextSibling, 0);
  436. selection.removeAllRanges();
  437. selection.addRange(newrange);
  438. // browser does not scroll the caret position into view, do it manually
  439. winUtils.scrollIntoView(newblock.nextSibling);
  440. }
  441. _letBrowserHandle = false;
  442. }else{ //press enter in the middle of P/DIV/Whatever/
  443. if(block.blockNode === block.blockContainer){
  444. block.blockNode.appendChild(newblock);
  445. }else{
  446. domConstruct.place(newblock, block.blockNode, "after");
  447. }
  448. _letBrowserHandle = false;
  449. // Clone any block level styles.
  450. if(block.blockNode.style){
  451. if(newblock.style){
  452. if(block.blockNode.style.cssText){
  453. newblock.style.cssText = block.blockNode.style.cssText;
  454. }
  455. }
  456. }
  457. // Okay, we probably have to split.
  458. rs = range.startContainer;
  459. var firstNodeMoved;
  460. if(rs && rs.nodeType == 3){
  461. // Text node, we have to split it.
  462. var nodeToMove, tNode;
  463. endOffset = range.endOffset;
  464. if(rs.length < endOffset){
  465. //We are not splitting the right node, try to locate the correct one
  466. ret = this._adjustNodeAndOffset(rs, endOffset);
  467. rs = ret.node;
  468. endOffset = ret.offset;
  469. }
  470. txt = rs.nodeValue;
  471. startNode = doc.createTextNode(txt.substring(0, endOffset));
  472. endNode = doc.createTextNode(txt.substring(endOffset, txt.length));
  473. // Place the split, then remove original nodes.
  474. domConstruct.place(startNode, rs, "before");
  475. domConstruct.place(endNode, rs, "after");
  476. domConstruct.destroy(rs);
  477. // Okay, we split the text. Now we need to see if we're
  478. // parented to the block element we're splitting and if
  479. // not, we have to split all the way up. Ugh.
  480. var parentC = startNode.parentNode;
  481. while(parentC !== block.blockNode){
  482. var tg = parentC.tagName;
  483. var newTg = doc.createElement(tg);
  484. // Clone over any 'style' data.
  485. if(parentC.style){
  486. if(newTg.style){
  487. if(parentC.style.cssText){
  488. newTg.style.cssText = parentC.style.cssText;
  489. }
  490. }
  491. }
  492. // If font also need to clone over any font data.
  493. if(parentC.tagName === "FONT"){
  494. if(parentC.color){
  495. newTg.color = parentC.color;
  496. }
  497. if(parentC.face){
  498. newTg.face = parentC.face;
  499. }
  500. if(parentC.size){ // this check was necessary on IE
  501. newTg.size = parentC.size;
  502. }
  503. }
  504. nodeToMove = endNode;
  505. while(nodeToMove){
  506. tNode = nodeToMove.nextSibling;
  507. newTg.appendChild(nodeToMove);
  508. nodeToMove = tNode;
  509. }
  510. domConstruct.place(newTg, parentC, "after");
  511. startNode = parentC;
  512. endNode = newTg;
  513. parentC = parentC.parentNode;
  514. }
  515. // Lastly, move the split out tags to the new block.
  516. // as they should now be split properly.
  517. nodeToMove = endNode;
  518. if(nodeToMove.nodeType == 1 || (nodeToMove.nodeType == 3 && nodeToMove.nodeValue)){
  519. // Non-blank text and non-text nodes need to clear out that blank space
  520. // before moving the contents.
  521. newblock.innerHTML = "";
  522. }
  523. firstNodeMoved = nodeToMove;
  524. while(nodeToMove){
  525. tNode = nodeToMove.nextSibling;
  526. newblock.appendChild(nodeToMove);
  527. nodeToMove = tNode;
  528. }
  529. }
  530. //lets move caret to the newly created block
  531. newrange = rangeapi.create(this.editor.window);
  532. var nodeForCursor;
  533. var innerMostFirstNodeMoved = firstNodeMoved;
  534. if(this.blockNodeForEnter !== 'BR'){
  535. while(innerMostFirstNodeMoved){
  536. nodeForCursor = innerMostFirstNodeMoved;
  537. tNode = innerMostFirstNodeMoved.firstChild;
  538. innerMostFirstNodeMoved = tNode;
  539. }
  540. if(nodeForCursor && nodeForCursor.parentNode){
  541. newblock = nodeForCursor.parentNode;
  542. newrange.setStart(newblock, 0);
  543. selection.removeAllRanges();
  544. selection.addRange(newrange);
  545. if(this.editor.height){
  546. winUtils.scrollIntoView(newblock);
  547. }
  548. if(has("mozilla")){
  549. // press enter in middle of P may leave a trailing <br/>, let's remove it later
  550. this._pressedEnterInBlock = block.blockNode;
  551. }
  552. }else{
  553. _letBrowserHandle = true;
  554. }
  555. }else{
  556. newrange.setStart(newblock, 0);
  557. selection.removeAllRanges();
  558. selection.addRange(newrange);
  559. if(this.editor.height){
  560. winUtils.scrollIntoView(newblock);
  561. }
  562. if(has("mozilla")){
  563. // press enter in middle of P may leave a trailing <br/>, let's remove it later
  564. this._pressedEnterInBlock = block.blockNode;
  565. }
  566. }
  567. }
  568. return _letBrowserHandle;
  569. },
  570. _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){
  571. // summary:
  572. // 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
  573. // the next text sibling until it locates the text node in which the offset refers to
  574. // node:
  575. // The node to check.
  576. // offset:
  577. // The position to find within the text node
  578. // tags:
  579. // private.
  580. while(node.length < offset && node.nextSibling && node.nextSibling.nodeType==3){
  581. //Adjust the offset and node in the case of multiple text nodes in a row
  582. offset = offset - node.length;
  583. node = node.nextSibling;
  584. }
  585. return {"node": node, "offset": offset};
  586. },
  587. removeTrailingBr: function(container){
  588. // summary:
  589. // If last child of container is a <br>, then remove it.
  590. // tags:
  591. // private
  592. var para = /P|DIV|LI/i.test(container.tagName) ?
  593. container : selectionapi.getParentOfType(container,['P','DIV','LI']);
  594. if(!para){ return; }
  595. if(para.lastChild){
  596. if((para.childNodes.length > 1 && para.lastChild.nodeType == 3 && /^[\s\xAD]*$/.test(para.lastChild.nodeValue)) ||
  597. para.lastChild.tagName=='BR'){
  598. domConstruct.destroy(para.lastChild);
  599. }
  600. }
  601. if(!para.childNodes.length){
  602. para.innerHTML=this.bogusHtmlContent;
  603. }
  604. }
  605. });
  606. });
  607. },
  608. 'dijit/BuxEditor':function(){
  609. define("dijit/BuxEditor", [
  610. "dojo/_base/array", // array.forEach
  611. "dojo/_base/declare", // declare
  612. "dojo/_base/Deferred", // Deferred
  613. "dojo/i18n", // i18n.getLocalization
  614. "dojo/dom-attr", // domAttr.set
  615. "dojo/dom-class", // domClass.add
  616. "dojo/dom-geometry",
  617. "dojo/dom-style", // domStyle.set, get
  618. "dojo/_base/event", // event.stop
  619. "dojo/keys", // keys.F1 keys.F15 keys.TAB
  620. "dojo/_base/lang", // lang.getObject lang.hitch
  621. "dojo/_base/sniff", // has("ie") has("mac") has("webkit")
  622. "dojo/string", // string.substitute
  623. "dojo/topic", // topic.publish()
  624. "dojo/_base/window", // win.withGlobal
  625. "./_base/focus", // dijit.getBookmark()
  626. "./_Container",
  627. "./Toolbar",
  628. "./ToolbarSeparator",
  629. "./layout/_LayoutWidget",
  630. "./form/ToggleButton",
  631. "./_editor/_Plugin",
  632. "./_editor/plugins/BuxEnterKeyHandling",
  633. "./_editor/html",
  634. "./_editor/range",
  635. "./_editor/BuxRichText",
  636. ".", // dijit._scopeName
  637. "dojo/i18n!./_editor/nls/commands"
  638. ], function(array, declare, Deferred, i18n, domAttr, domClass, domGeometry, domStyle,
  639. event, keys, lang, has, string, topic, win,
  640. focusBase, _Container, Toolbar, ToolbarSeparator, _LayoutWidget, ToggleButton,
  641. _Plugin, BuxEnterKeyHandling, html, rangeapi, BuxRichText, dijit){
  642. // module:
  643. // dijit/BuxEditor
  644. // summary:
  645. // A rich text Editing widget
  646. var Editor = declare("dijit.BuxEditor", BuxRichText, {
  647. // summary:
  648. // A rich text Editing widget
  649. //
  650. // description:
  651. // This widget provides basic WYSIWYG editing features, based on the browser's
  652. // underlying rich text editing capability, accompanied by a toolbar (`dijit.Toolbar`).
  653. // A plugin model is available to extend the editor's capabilities as well as the
  654. // the options available in the toolbar. Content generation may vary across
  655. // browsers, and clipboard operations may have different results, to name
  656. // a few limitations. Note: this widget should not be used with the HTML
  657. // &lt;TEXTAREA&gt; tag -- see dijit._editor.BuxRichText for details.
  658. // plugins: [const] Object[]
  659. // A list of plugin names (as strings) or instances (as objects)
  660. // for this widget.
  661. //
  662. // When declared in markup, it might look like:
  663. // | plugins="['bold',{name:'dijit._editor.plugins.FontChoice', command:'fontName', generic:true}]"
  664. plugins: null,
  665. // extraPlugins: [const] Object[]
  666. // A list of extra plugin names which will be appended to plugins array
  667. extraPlugins: null,
  668. constructor: function(){
  669. // summary:
  670. // Runs on widget initialization to setup arrays etc.
  671. // tags:
  672. // private
  673. if(!lang.isArray(this.plugins)){
  674. this.plugins=["undo","redo","|","cut","copy","paste","|","bold","italic","underline","strikethrough","|",
  675. "insertOrderedList","insertUnorderedList","indent","outdent","|","justifyLeft","justifyRight","justifyCenter","justifyFull",
  676. BuxEnterKeyHandling /*, "createLink"*/];
  677. }
  678. this._plugins=[];
  679. this._editInterval = this.editActionInterval * 1000;
  680. //IE will always lose focus when other element gets focus, while for FF and safari,
  681. //when no iframe is used, focus will be lost whenever another element gets focus.
  682. //For IE, we can connect to onBeforeDeactivate, which will be called right before
  683. //the focus is lost, so we can obtain the selected range. For other browsers,
  684. //no equivalent of onBeforeDeactivate, so we need to do two things to make sure
  685. //selection is properly saved before focus is lost: 1) when user clicks another
  686. //element in the page, in which case we listen to mousedown on the entire page and
  687. //see whether user clicks out of a focus editor, if so, save selection (focus will
  688. //only lost after onmousedown event is fired, so we can obtain correct caret pos.)
  689. //2) when user tabs away from the editor, which is handled in onKeyDown below.
  690. if(has("ie") || has("trident")){
  691. this.events.push("onBeforeDeactivate");
  692. this.events.push("onBeforeActivate");
  693. }
  694. },
  695. postMixInProperties: function(){
  696. // summary:
  697. // Extension to make sure a deferred is in place before certain functions
  698. // execute, like making sure all the plugins are properly inserted.
  699. // Set up a deferred so that the value isn't applied to the editor
  700. // until all the plugins load, needed to avoid timing condition
  701. // reported in #10537.
  702. this.setValueDeferred = new Deferred();
  703. this.inherited(arguments);
  704. },
  705. postCreate: function(){
  706. //for custom undo/redo, if enabled.
  707. this._steps=this._steps.slice(0);
  708. this._undoedSteps=this._undoedSteps.slice(0);
  709. if(lang.isArray(this.extraPlugins)){
  710. this.plugins=this.plugins.concat(this.extraPlugins);
  711. }
  712. this.inherited(arguments);
  713. this.commands = i18n.getLocalization("dijit._editor", "commands", this.lang);
  714. if(!this.toolbar){
  715. // if we haven't been assigned a toolbar, create one
  716. this.toolbar = new Toolbar({
  717. dir: this.dir,
  718. lang: this.lang
  719. });
  720. this.header.appendChild(this.toolbar.domNode);
  721. }
  722. array.forEach(this.plugins, this.addPlugin, this);
  723. // Okay, denote the value can now be set.
  724. this.setValueDeferred.callback(true);
  725. domClass.add(this.iframe.parentNode, "dijitEditorIFrameContainer");
  726. domClass.add(this.iframe, "dijitEditorIFrame");
  727. domAttr.set(this.iframe, "allowTransparency", true);
  728. if(has("webkit")){
  729. // Disable selecting the entire editor by inadvertent double-clicks.
  730. // on buttons, title bar, etc. Otherwise clicking too fast on
  731. // a button such as undo/redo selects the entire editor.
  732. domStyle.set(this.domNode, "KhtmlUserSelect", "none");
  733. }
  734. this.toolbar.startup();
  735. this.onNormalizedDisplayChanged(); //update toolbar button status
  736. },
  737. destroy: function(){
  738. array.forEach(this._plugins, function(p){
  739. if(p && p.destroy){
  740. p.destroy();
  741. }
  742. });
  743. this._plugins=[];
  744. this.toolbar.destroyRecursive();
  745. delete this.toolbar;
  746. this.inherited(arguments);
  747. },
  748. addPlugin: function(/*String||Object||Function*/plugin, /*Integer?*/index){
  749. // summary:
  750. // takes a plugin name as a string or a plugin instance and
  751. // adds it to the toolbar and associates it with this editor
  752. // instance. The resulting plugin is added to the Editor's
  753. // plugins array. If index is passed, it's placed in the plugins
  754. // array at that index. No big magic, but a nice helper for
  755. // passing in plugin names via markup.
  756. //
  757. // plugin: String, args object, plugin instance, or plugin constructor
  758. //
  759. // args:
  760. // This object will be passed to the plugin constructor
  761. //
  762. // index: Integer
  763. // Used when creating an instance from
  764. // something already in this.plugins. Ensures that the new
  765. // instance is assigned to this.plugins at that index.
  766. var args=lang.isString(plugin)?{name:plugin}:lang.isFunction(plugin)?{ctor:plugin}:plugin;
  767. if(!args.setEditor){
  768. var o={"args":args,"plugin":null,"editor":this};
  769. if(args.name){
  770. // search registry for a plugin factory matching args.name, if it's not there then
  771. // fallback to 1.0 API:
  772. // ask all loaded plugin modules to fill in o.plugin if they can (ie, if they implement args.name)
  773. // remove fallback for 2.0.
  774. if(_Plugin.registry[args.name]){
  775. o.plugin = _Plugin.registry[args.name](args);
  776. }else{
  777. topic.publish(dijit._scopeName + ".Editor.getPlugin", o); // publish
  778. }
  779. }
  780. if(!o.plugin){
  781. var pc = args.ctor || lang.getObject(args.name);
  782. if(pc){
  783. o.plugin=new pc(args);
  784. }
  785. }
  786. if(!o.plugin){
  787. console.warn('Cannot find plugin',plugin);
  788. return;
  789. }
  790. plugin=o.plugin;
  791. }
  792. if(arguments.length > 1){
  793. this._plugins[index] = plugin;
  794. }else{
  795. this._plugins.push(plugin);
  796. }
  797. plugin.setEditor(this);
  798. if(lang.isFunction(plugin.setToolbar)){
  799. plugin.setToolbar(this.toolbar);
  800. }
  801. },
  802. //the following 2 functions are required to make the editor play nice under a layout widget, see #4070
  803. resize: function(size){
  804. // summary:
  805. // Resize the editor to the specified size, see `dijit.layout._LayoutWidget.resize`
  806. if(size){
  807. // we've been given a height/width for the entire editor (toolbar + contents), calls layout()
  808. // to split the allocated size between the toolbar and the contents
  809. _LayoutWidget.prototype.resize.apply(this, arguments);
  810. }
  811. /*
  812. else{
  813. // do nothing, the editor is already laid out correctly. The user has probably specified
  814. // the height parameter, which was used to set a size on the iframe
  815. }
  816. */
  817. },
  818. layout: function(){
  819. // summary:
  820. // Called from `dijit.layout._LayoutWidget.resize`. This shouldn't be called directly
  821. // tags:
  822. // protected
  823. // Converts the iframe (or rather the <div> surrounding it) to take all the available space
  824. // except what's needed for the header (toolbars) and footer (breadcrumbs, etc).
  825. // A class was added to the iframe container and some themes style it, so we have to
  826. // calc off the added margins and padding too. See tracker: #10662
  827. var areaHeight = (this._contentBox.h -
  828. (this.getHeaderHeight() + this.getFooterHeight() +
  829. domGeometry.getPadBorderExtents(this.iframe.parentNode).h +
  830. domGeometry.getMarginExtents(this.iframe.parentNode).h));
  831. this.editingArea.style.height = areaHeight + "px";
  832. if(this.iframe){
  833. this.iframe.style.height="100%";
  834. }
  835. this._layoutMode = true;
  836. },
  837. _onIEMouseDown: function(/*Event*/ e){
  838. // summary:
  839. // IE only to prevent 2 clicks to focus
  840. // tags:
  841. // private
  842. var outsideClientArea;
  843. // IE 8's componentFromPoint is broken, which is a shame since it
  844. // was smaller code, but oh well. We have to do this brute force
  845. // to detect if the click was scroller or not.
  846. var b = this.document.body;
  847. var clientWidth = b.clientWidth;
  848. var clientHeight = b.clientHeight;
  849. var clientLeft = b.clientLeft;
  850. var offsetWidth = b.offsetWidth;
  851. var offsetHeight = b.offsetHeight;
  852. var offsetLeft = b.offsetLeft;
  853. //Check for vertical scroller click.
  854. if(/^rtl$/i.test(b.dir || "")){
  855. if(clientWidth < offsetWidth && e.x > clientWidth && e.x < offsetWidth){
  856. // Check the click was between width and offset width, if so, scroller
  857. outsideClientArea = true;
  858. }
  859. }else{
  860. // RTL mode, we have to go by the left offsets.
  861. if(e.x < clientLeft && e.x > offsetLeft){
  862. // Check the click was between width and offset width, if so, scroller
  863. outsideClientArea = true;
  864. }
  865. }
  866. if(!outsideClientArea){
  867. // Okay, might be horiz scroller, check that.
  868. if(clientHeight < offsetHeight && e.y > clientHeight && e.y < offsetHeight){
  869. // Horizontal scroller.
  870. outsideClientArea = true;
  871. }
  872. }
  873. if(!outsideClientArea){
  874. delete this._cursorToStart; // Remove the force to cursor to start position.
  875. delete this._savedSelection; // new mouse position overrides old selection
  876. if(e.target.tagName == "BODY"){
  877. setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0);
  878. }
  879. this.inherited(arguments);
  880. }
  881. },
  882. onBeforeActivate: function(){
  883. this._restoreSelection();
  884. },
  885. onBeforeDeactivate: function(e){
  886. // summary:
  887. // Called on IE right before focus is lost. Saves the selected range.
  888. // tags:
  889. // private
  890. if(this.customUndo){
  891. this.endEditing(true);
  892. }
  893. //in IE, the selection will be lost when other elements get focus,
  894. //let's save focus before the editor is deactivated
  895. if(e.target.tagName != "BODY"){
  896. this._saveSelection();
  897. }
  898. //console.log('onBeforeDeactivate',this);
  899. },
  900. /* beginning of custom undo/redo support */
  901. // customUndo: Boolean
  902. // Whether we shall use custom undo/redo support instead of the native
  903. // browser support. By default, we now use custom undo. It works better
  904. // than native browser support and provides a consistent behavior across
  905. // browsers with a minimal performance hit. We already had the hit on
  906. // the slowest browser, IE, anyway.
  907. customUndo: true,
  908. // editActionInterval: Integer
  909. // When using customUndo, not every keystroke will be saved as a step.
  910. // Instead typing (including delete) will be grouped together: after
  911. // a user stops typing for editActionInterval seconds, a step will be
  912. // saved; if a user resume typing within editActionInterval seconds,
  913. // the timeout will be restarted. By default, editActionInterval is 3
  914. // seconds.
  915. editActionInterval: 3,
  916. beginEditing: function(cmd){
  917. // summary:
  918. // Called to note that the user has started typing alphanumeric characters, if it's not already noted.
  919. // Deals with saving undo; see editActionInterval parameter.
  920. // tags:
  921. // private
  922. if(!this._inEditing){
  923. this._inEditing=true;
  924. this._beginEditing(cmd);
  925. }
  926. if(this.editActionInterval>0){
  927. if(this._editTimer){
  928. clearTimeout(this._editTimer);
  929. }
  930. this._editTimer = setTimeout(lang.hitch(this, this.endEditing), this._editInterval);
  931. }
  932. },
  933. // TODO: declaring these in the prototype is meaningless, just create in the constructor/postCreate
  934. _steps:[],
  935. _undoedSteps:[],
  936. execCommand: function(cmd){
  937. // summary:
  938. // Main handler for executing any commands to the editor, like paste, bold, etc.
  939. // Called by plugins, but not meant to be called by end users.
  940. // tags:
  941. // protected
  942. if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
  943. return this[cmd]();
  944. }else{
  945. if(this.customUndo){
  946. this.endEditing();
  947. this._beginEditing();
  948. }
  949. var r = this.inherited(arguments);
  950. if(this.customUndo){
  951. this._endEditing();
  952. }
  953. return r;
  954. }
  955. },
  956. _pasteImpl: function(){
  957. // summary:
  958. // Over-ride of paste command control to make execCommand cleaner
  959. // tags:
  960. // Protected
  961. return this._clipboardCommand("paste");
  962. },
  963. _cutImpl: function(){
  964. // summary:
  965. // Over-ride of cut command control to make execCommand cleaner
  966. // tags:
  967. // Protected
  968. return this._clipboardCommand("cut");
  969. },
  970. _copyImpl: function(){
  971. // summary:
  972. // Over-ride of copy command control to make execCommand cleaner
  973. // tags:
  974. // Protected
  975. return this._clipboardCommand("copy");
  976. },
  977. _clipboardCommand: function(cmd){
  978. // summary:
  979. // Function to handle processing clipboard commands (or at least try to).
  980. // tags:
  981. // Private
  982. var r;
  983. try{
  984. // Try to exec the superclass exec-command and see if it works.
  985. r = this.document.execCommand(cmd, false, null);
  986. if(has("webkit") && !r){ //see #4598: webkit does not guarantee clipboard support from js
  987. throw { code: 1011 }; // throw an object like Mozilla's error
  988. }
  989. }catch(e){
  990. //TODO: when else might we get an exception? Do we need the Mozilla test below?
  991. if(e.code == 1011 /* Mozilla: service denied */){
  992. // Warn user of platform limitation. Cannot programmatically access clipboard. See ticket #4136
  993. var sub = string.substitute,
  994. accel = {cut:'X', copy:'C', paste:'V'};
  995. alert(sub(this.commands.systemShortcut,
  996. [this.commands[cmd], sub(this.commands[has("mac") ? 'appleKey' : 'ctrlKey'], [accel[cmd]])]));
  997. }
  998. r = false;
  999. }
  1000. return r;
  1001. },
  1002. queryCommandEnabled: function(cmd){
  1003. // summary:
  1004. // Returns true if specified editor command is enabled.
  1005. // Used by the plugins to know when to highlight/not highlight buttons.
  1006. // tags:
  1007. // protected
  1008. if(this.customUndo && (cmd == 'undo' || cmd == 'redo')){
  1009. return cmd == 'undo' ? (this._steps.length > 1) : (this._undoedSteps.length > 0);
  1010. }else{
  1011. return this.inherited(arguments);
  1012. }
  1013. },
  1014. _moveToBookmark: function(b){
  1015. // summary:
  1016. // Selects the text specified in bookmark b
  1017. // tags:
  1018. // private
  1019. var bookmark = b.mark;
  1020. var mark = b.mark;
  1021. var col = b.isCollapsed;
  1022. var r, sNode, eNode, sel;
  1023. if(mark){
  1024. if(has("ie") < 9){
  1025. if(lang.isArray(mark)){
  1026. //IE CONTROL, have to use the native bookmark.
  1027. bookmark = [];
  1028. array.forEach(mark,function(n){
  1029. bookmark.push(rangeapi.getNode(n,this.editNode));
  1030. },this);
  1031. win.withGlobal(this.window,'moveToBookmark',dijit,[{mark: bookmark, isCollapsed: col}]);
  1032. }else{
  1033. if(mark.startContainer && mark.endContainer){
  1034. // Use the pseudo WC3 range API. This works better for positions
  1035. // than the IE native bookmark code.
  1036. sel = rangeapi.getSelection(this.window);
  1037. if(sel && sel.removeAllRanges){
  1038. sel.removeAllRanges();
  1039. r = rangeapi.create(this.window);
  1040. sNode = rangeapi.getNode(mark.startContainer,this.editNode);
  1041. eNode = rangeapi.getNode(mark.endContainer,this.editNode);
  1042. if(sNode && eNode){
  1043. // Okay, we believe we found the position, so add it into the selection
  1044. // There are cases where it may not be found, particularly in undo/redo, when
  1045. // IE changes the underlying DOM on us (wraps text in a <p> tag or similar.
  1046. // So, in those cases, don't bother restoring selection.
  1047. r.setStart(sNode,mark.startOffset);
  1048. r.setEnd(eNode,mark.endOffset);
  1049. sel.addRange(r);
  1050. }
  1051. }
  1052. }
  1053. }
  1054. }else{//w3c range
  1055. sel = rangeapi.getSelection(this.window);
  1056. if(sel && sel.removeAllRanges){
  1057. sel.removeAllRanges();
  1058. r = rangeapi.create(this.window);
  1059. sNode = rangeapi.getNode(mark.startContainer,this.editNode);
  1060. eNode = rangeapi.getNode(mark.endContainer,this.editNode);
  1061. if(sNode && eNode){
  1062. // Okay, we believe we found the position, so add it into the selection
  1063. // There are cases where it may not be found, particularly in undo/redo, when
  1064. // formatting as been done and so on, so don't restore selection then.
  1065. r.setStart(sNode,mark.startOffset);
  1066. r.setEnd(eNode,mark.endOffset);
  1067. sel.addRange(r);
  1068. }
  1069. }
  1070. }
  1071. }
  1072. },
  1073. _changeToStep: function(from, to){
  1074. // summary:
  1075. // Reverts editor to "to" setting, from the undo stack.
  1076. // tags:
  1077. // private
  1078. this.setValue(to.text);
  1079. var b=to.bookmark;
  1080. if(!b){ return; }
  1081. this._moveToBookmark(b);
  1082. },
  1083. undo: function(){
  1084. // summary:
  1085. // Handler for editor undo (ex: ctrl-z) operation
  1086. // tags:
  1087. // private
  1088. //console.log('undo');
  1089. var ret = false;
  1090. if(!this._undoRedoActive){
  1091. this._undoRedoActive = true;
  1092. this.endEditing(true);
  1093. var s=this._steps.pop();
  1094. if(s && this._steps.length>0){
  1095. this.focus();
  1096. this._changeToStep(s,this._steps[this._steps.length-1]);
  1097. this._undoedSteps.push(s);
  1098. this.onDisplayChanged();
  1099. delete this._undoRedoActive;
  1100. ret = true;
  1101. }
  1102. delete this._undoRedoActive;
  1103. }
  1104. return ret;
  1105. },
  1106. redo: function(){
  1107. // summary:
  1108. // Handler for editor redo (ex: ctrl-y) operation
  1109. // tags:
  1110. // private
  1111. //console.log('redo');
  1112. var ret = false;
  1113. if(!this._undoRedoActive){
  1114. this._undoRedoActive = true;
  1115. this.endEditing(true);
  1116. var s=this._undoedSteps.pop();
  1117. if(s && this._steps.length>0){
  1118. this.focus();
  1119. this._changeToStep(this._steps[this._steps.length-1],s);
  1120. this._steps.push(s);
  1121. this.onDisplayChanged();
  1122. ret = true;
  1123. }
  1124. delete this._undoRedoActive;
  1125. }
  1126. return ret;
  1127. },
  1128. endEditing: function(ignore_caret){
  1129. // summary:
  1130. // Called to note that the user has stopped typing alphanumeric characters, if it's not already noted.
  1131. // Deals with saving undo; see editActionInterval parameter.
  1132. // tags:
  1133. // private
  1134. if(this._editTimer){
  1135. clearTimeout(this._editTimer);
  1136. }
  1137. if(this._inEditing){
  1138. this._endEditing(ignore_caret);
  1139. this._inEditing=false;
  1140. }
  1141. },
  1142. _getBookmark: function(){
  1143. // summary:
  1144. // Get the currently selected text
  1145. // tags:
  1146. // protected
  1147. var b=win.withGlobal(this.window,focusBase.getBookmark);
  1148. var tmp=[];
  1149. if(b && b.mark){
  1150. var mark = b.mark;
  1151. if(has("ie") < 9){
  1152. // Try to use the pseudo range API on IE for better accuracy.
  1153. var sel = rangeapi.getSelection(this.window);
  1154. if(!lang.isArray(mark)){
  1155. if(sel){
  1156. var range;
  1157. if(sel.rangeCount){
  1158. range = sel.getRangeAt(0);
  1159. }
  1160. if(range){
  1161. b.mark = range.cloneRange();
  1162. }else{
  1163. b.mark = win.withGlobal(this.window,focusBase.getBookmark);
  1164. }
  1165. }
  1166. }else{
  1167. // Control ranges (img, table, etc), handle differently.
  1168. array.forEach(b.mark,function(n){
  1169. tmp.push(rangeapi.getIndex(n,this.editNode).o);
  1170. },this);
  1171. b.mark = tmp;
  1172. }
  1173. }
  1174. try{
  1175. if(b.mark && b.mark.startContainer){
  1176. tmp=rangeapi.getIndex(b.mark.startContainer,this.editNode).o;
  1177. b.mark={startContainer:tmp,
  1178. startOffset:b.mark.startOffset,
  1179. endContainer:b.mark.endContainer===b.mark.startContainer?tmp:rangeapi.getIndex(b.mark.endContainer,this.editNode).o,
  1180. endOffset:b.mark.endOffset};
  1181. }
  1182. }catch(e){
  1183. b.mark = null;
  1184. }
  1185. }
  1186. return b;
  1187. },
  1188. _beginEditing: function(){
  1189. // summary:
  1190. // Called when the user starts typing alphanumeric characters.
  1191. // Deals with saving undo; see editActionInterval parameter.
  1192. // tags:
  1193. // private
  1194. if(this._steps.length === 0){
  1195. // You want to use the editor content without post filtering
  1196. // to make sure selection restores right for the 'initial' state.
  1197. // and undo is called. So not using this.value, as it was 'processed'
  1198. // and the line-up for selections may have been altered.
  1199. this._steps.push({'text':html.getChildrenHtml(this.editNode),'bookmark':this._getBookmark()});
  1200. }
  1201. },
  1202. _endEditing: function(){
  1203. // summary:
  1204. // Called when the user stops typing alphanumeric characters.
  1205. // Deals with saving undo; see editActionInterval parameter.
  1206. // tags:
  1207. // private
  1208. // Avoid filtering to make sure selections restore.
  1209. var v = html.getChildrenHtml(this.editNode);
  1210. this._undoedSteps=[];//clear undoed steps
  1211. this._steps.push({text: v, bookmark: this._getBookmark()});
  1212. },
  1213. onKeyDown: function(e){
  1214. // summary:
  1215. // Handler for onkeydown event.
  1216. // tags:
  1217. // private
  1218. //We need to save selection if the user TAB away from this editor
  1219. //no need to call _saveSelection for IE, as that will be taken care of in onBeforeDeactivate
  1220. if(!has("ie") && !this.iframe && e.keyCode == keys.TAB && !this.tabIndent){
  1221. this._saveSelection();
  1222. }
  1223. if(!this.customUndo){
  1224. this.inherited(arguments);
  1225. return;
  1226. }
  1227. var k = e.keyCode;
  1228. if(e.ctrlKey && !e.altKey){//undo and redo only if the special right Alt + z/y are not pressed #5892
  1229. if(k == 90 || k == 122){ //z
  1230. event.stop(e);
  1231. this.undo();
  1232. return;
  1233. }else if(k == 89 || k == 121){ //y
  1234. event.stop(e);
  1235. this.redo();
  1236. return;
  1237. }
  1238. }
  1239. this.inherited(arguments);
  1240. switch(k){
  1241. case keys.ENTER:
  1242. case keys.BACKSPACE:
  1243. case keys.DELETE:
  1244. this.beginEditing();
  1245. break;
  1246. case 88: //x
  1247. case 86: //v
  1248. if(e.ctrlKey && !e.altKey && !e.metaKey){
  1249. this.endEditing();//end current typing step if any
  1250. if(e.keyCode == 88){
  1251. this.beginEditing('cut');
  1252. //use timeout to trigger after the cut is complete
  1253. setTimeout(lang.hitch(this, this.endEditing), 1);
  1254. }else{
  1255. this.beginEditing('paste');
  1256. //use timeout to trigger after the paste is complete
  1257. setTimeout(lang.hitch(this, this.endEditing), 1);
  1258. }
  1259. break;
  1260. }
  1261. //pass through
  1262. default:
  1263. if(!e.ctrlKey && !e.altKey && !e.metaKey && (e.keyCode<keys.F1 || e.keyCode>keys.F15)){
  1264. this.beginEditing();
  1265. break;
  1266. }
  1267. //pass through
  1268. case keys.ALT:
  1269. this.endEditing();
  1270. break;
  1271. case keys.UP_ARROW:
  1272. case keys.DOWN_ARROW:
  1273. case keys.LEFT_ARROW:
  1274. case keys.RIGHT_ARROW:
  1275. case keys.HOME:
  1276. case keys.END:
  1277. case keys.PAGE_UP:
  1278. case keys.PAGE_DOWN:
  1279. this.endEditing(true);
  1280. break;
  1281. //maybe ctrl+backspace/delete, so don't endEditing when ctrl is pressed
  1282. case keys.CTRL:
  1283. case keys.SHIFT:
  1284. case keys.TAB:
  1285. break;
  1286. }
  1287. },
  1288. _onBlur: function(){
  1289. // summary:
  1290. // Called from focus manager when focus has moved away from this editor
  1291. // tags:
  1292. // protected
  1293. //this._saveSelection();
  1294. this.inherited(arguments);
  1295. this.endEditing(true);
  1296. },
  1297. _saveSelection: function(){
  1298. // summary:
  1299. // Save the currently selected text in _savedSelection attribute
  1300. // tags:
  1301. // private
  1302. try{
  1303. this._savedSelection=this._getBookmark();
  1304. }catch(e){ /* Squelch any errors that occur if selection save occurs due to being hidden simultaneously. */}
  1305. },
  1306. _restoreSelection: function(){
  1307. // summary:
  1308. // Re-select the text specified in _savedSelection attribute;
  1309. // see _saveSelection().
  1310. // tags:
  1311. // private
  1312. if(this._savedSelection){
  1313. // Clear off cursor to start, we're deliberately going to a selection.
  1314. delete this._cursorToStart;
  1315. // only restore the selection if the current range is collapsed
  1316. // if not collapsed, then it means the editor does not lose
  1317. // selection and there is no need to restore it
  1318. if(win.withGlobal(this.window,'isCollapsed',dijit)){
  1319. this._moveToBookmark(this._savedSelection);
  1320. }
  1321. delete this._savedSelection;
  1322. }
  1323. },
  1324. onClick: function(){
  1325. // summary:
  1326. // Handler for when editor is clicked
  1327. // tags:
  1328. // protected
  1329. this.endEditing(true);
  1330. this.inherited(arguments);
  1331. },
  1332. replaceValue: function(/*String*/ html){
  1333. // summary:
  1334. // over-ride of replaceValue to support custom undo and stack maintenance.
  1335. // tags:
  1336. // protected
  1337. if(!this.customUndo){
  1338. this.inherited(arguments);
  1339. }else{
  1340. if(this.isClosed){
  1341. this.setValue(html);
  1342. }else{
  1343. this.beginEditing();
  1344. if(!html){
  1345. html = "&#160;"; // &nbsp;
  1346. }
  1347. this.setValue(html);
  1348. this.endEditing();
  1349. }
  1350. }
  1351. },
  1352. _setDisabledAttr: function(/*Boolean*/ value){
  1353. var disableFunc = lang.hitch(this, function(){
  1354. if((!this.disabled && value) || (!this._buttonEnabledPlugins && value)){
  1355. // Disable editor: disable all enabled buttons and remember that list
  1356. array.forEach(this._plugins, function(p){
  1357. p.set("disabled", true);
  1358. });
  1359. }else if(this.disabled && !value){
  1360. // Restore plugins to being active.
  1361. array.forEach(this._plugins, function(p){
  1362. p.set("disabled", false);
  1363. });
  1364. }
  1365. });
  1366. this.setValueDeferred.addCallback(disableFunc);
  1367. this.inherited(arguments);
  1368. },
  1369. _setStateClass: function(){
  1370. try{
  1371. this.inherited(arguments);
  1372. // Let theme set the editor's text color based on editor enabled/disabled state.
  1373. // We need to jump through hoops because the main document (where the theme CSS is)
  1374. // is separate from the iframe's document.
  1375. if(this.document && this.document.body){
  1376. domStyle.set(this.document.body, "color", domStyle.get(this.iframe, "color"));
  1377. }
  1378. }catch(e){ /* Squelch any errors caused by focus change if hidden during a state change */}
  1379. }
  1380. });
  1381. // Register the "default plugins", ie, the built-in editor commands
  1382. function simplePluginFactory(args){
  1383. return new _Plugin({ command: args.name });
  1384. }
  1385. function togglePluginFactory(args){
  1386. return new _Plugin({ buttonClass: ToggleButton, command: args.name });
  1387. }
  1388. lang.mixin(_Plugin.registry, {
  1389. "undo": simplePluginFactory,
  1390. "redo": simplePluginFactory,
  1391. "cut": simplePluginFactory,
  1392. "copy": simplePluginFactory,
  1393. "paste": simplePluginFactory,
  1394. "insertOrderedList": simplePluginFactory,
  1395. "insertUnorderedList": simplePluginFactory,
  1396. "indent": simplePluginFactory,
  1397. "outdent": simplePluginFactory,
  1398. "justifyCenter": simplePluginFactory,
  1399. "justifyFull": simplePluginFactory,
  1400. "justifyLeft": simplePluginFactory,
  1401. "justifyRight": simplePluginFactory,
  1402. "delete": simplePluginFactory,
  1403. "selectAll": simplePluginFactory,
  1404. "removeFormat": simplePluginFactory,
  1405. "unlink": simplePluginFactory,
  1406. "insertHorizontalRule": simplePluginFactory,
  1407. "bold": togglePluginFactory,
  1408. "italic": togglePluginFactory,
  1409. "underline": togglePluginFactory,
  1410. "strikethrough": togglePluginFactory,
  1411. "subscript": togglePluginFactory,
  1412. "superscript": togglePluginFactory,
  1413. "|": function(){
  1414. return new _Plugin({ button: new ToolbarSeparator(), setEditor: function(editor){this.editor = editor;}});
  1415. }
  1416. });
  1417. return Editor;
  1418. });
  1419. },
  1420. 'dijit/_editor/_Plugin':function(){
  1421. define("dijit/_editor/_Plugin", [
  1422. "dojo/_base/connect", // connect.connect
  1423. "dojo/_base/declare", // declare
  1424. "dojo/_base/lang", // lang.mixin, lang.hitch
  1425. "../form/Button"
  1426. ], function(connect, declare, lang, Button){
  1427. // module:
  1428. // dijit/_editor/_Plugin
  1429. // summary:
  1430. // Base class for a "plugin" to the editor, which is usually
  1431. // a single button on the Toolbar and some associated code
  1432. var _Plugin = declare("dijit._editor._Plugin", null, {
  1433. // summary:
  1434. // Base class for a "plugin" to the editor, which is usually
  1435. // a single button on the Toolbar and some associated code
  1436. constructor: function(/*Object?*/args){
  1437. this.params = args || {};
  1438. lang.mixin(this, this.params);
  1439. this._connects=[];
  1440. this._attrPairNames = {};
  1441. },
  1442. // editor: [const] dijit.Editor
  1443. // Points to the parent editor
  1444. editor: null,
  1445. // iconClassPrefix: [const] String
  1446. // The CSS class name for the button node is formed from `iconClassPrefix` and `command`
  1447. iconClassPrefix: "dijitEditorIcon",
  1448. // button: dijit._Widget?
  1449. // Pointer to `dijit.form.Button` or other widget (ex: `dijit.form.FilteringSelect`)
  1450. // that is added to the toolbar to control this plugin.
  1451. // If not specified, will be created on initialization according to `buttonClass`
  1452. button: null,
  1453. // command: String
  1454. // String like "insertUnorderedList", "outdent", "justifyCenter", etc. that represents an editor command.
  1455. // Passed to editor.execCommand() if `useDefaultCommand` is true.
  1456. command: "",
  1457. // useDefaultCommand: Boolean
  1458. // If true, this plugin executes by calling Editor.execCommand() with the argument specified in `command`.
  1459. useDefaultCommand: true,
  1460. // buttonClass: Widget Class
  1461. // Class of widget (ex: dijit.form.Button or dijit.form.FilteringSelect)
  1462. // that is added to the toolbar to control this plugin.
  1463. // This is used to instantiate the button, unless `button` itself is specified directly.
  1464. buttonClass: Button,
  1465. // disabled: Boolean
  1466. // Flag to indicate if this plugin has been disabled and should do nothing
  1467. // helps control button state, among other things. Set via the setter api.
  1468. disabled: false,
  1469. getLabel: function(/*String*/key){
  1470. // summary:
  1471. // Returns the label to use for the button
  1472. // tags:
  1473. // private
  1474. return this.editor.commands[key]; // String
  1475. },
  1476. _initButton: function(){
  1477. // summary:
  1478. // Initialize the button or other widget that will control this plugin.
  1479. // This code only works for plugins controlling built-in commands in the editor.
  1480. // tags:
  1481. // protected extension
  1482. if(this.command.length){
  1483. var label = this.getLabel(this.command),
  1484. editor = this.editor,
  1485. className = this.iconClassPrefix+" "+this.iconClassPrefix + this.command.charAt(0).toUpperCase() + this.command.substr(1);
  1486. if(!this.button){
  1487. var props = lang.mixin({
  1488. label: label,
  1489. dir: editor.dir,
  1490. lang: editor.lang,
  1491. showLabel: false,
  1492. iconClass: className,
  1493. dropDown: this.dropDown,
  1494. tabIndex: "-1"
  1495. }, this.params || {});
  1496. this.button = new this.buttonClass(props);
  1497. }
  1498. }
  1499. if(this.get("disabled") && this.button){
  1500. this.button.set("disabled", this.get("disabled"));
  1501. }
  1502. },
  1503. destroy: function(){
  1504. // summary:
  1505. // Destroy this plugin
  1506. var h;
  1507. while(h = this._connects.pop()){ h.remove(); }
  1508. if(this.dropDown){
  1509. this.dropDown.destroyRecursive();
  1510. }
  1511. },
  1512. connect: function(o, f, tf){
  1513. // summary:
  1514. // Make a connect.connect() that is automatically disconnected when this plugin is destroyed.
  1515. // Similar to `dijit._Widget.connect`.
  1516. // tags:
  1517. // protected
  1518. this._connects.push(connect.connect(o, f, this, tf));
  1519. },
  1520. updateState: function(){
  1521. // summary:
  1522. // Change state of the plugin to respond to events in the editor.
  1523. // description:
  1524. // This is called on meaningful events in the editor, such as change of selection
  1525. // or caret position (but not simple typing of alphanumeric keys). It gives the
  1526. // plugin a chance to update the CSS of its button.
  1527. //
  1528. // For example, the "bold" plugin will highlight/unhighlight the bold button depending on whether the
  1529. // characters next to the caret are bold or not.
  1530. //
  1531. // Only makes sense when `useDefaultCommand` is true, as it calls Editor.queryCommandEnabled(`command`).
  1532. var e = this.editor,
  1533. c = this.command,
  1534. checked, enabled;
  1535. if(!e || !e.isLoaded || !c.length){ return; }
  1536. var disabled = this.get("disabled");
  1537. if(this.button){
  1538. try{
  1539. enabled = !disabled && e.queryCommandEnabled(c);
  1540. if(this.enabled !== enabled){
  1541. this.enabled = enabled;
  1542. this.button.set('disabled', !enabled);
  1543. }
  1544. if(typeof this.button.checked == 'boolean'){
  1545. checked = e.queryCommandState(c);
  1546. if(this.checked !== checked){
  1547. this.checked = checked;
  1548. this.button.set('checked', e.queryCommandState(c));
  1549. }
  1550. }
  1551. }catch(e){
  1552. console.log(e); // FIXME: we shouldn't have debug statements in our code. Log as an error?
  1553. }
  1554. }
  1555. },
  1556. setEditor: function(/*dijit.Editor*/ editor){
  1557. // summary:
  1558. // Tell the plugin which Editor it is associated with.
  1559. // TODO: refactor code to just pass editor to constructor.
  1560. // FIXME: detach from previous editor!!
  1561. this.editor = editor;
  1562. // FIXME: prevent creating this if we don't need to (i.e., editor can't handle our command)
  1563. this._initButton();
  1564. // Processing for buttons that execute by calling editor.execCommand()
  1565. if(this.button && this.useDefaultCommand){
  1566. if(this.editor.queryCommandAvailable(this.command)){
  1567. this.connect(this.button, "onClick",
  1568. lang.hitch(this.editor, "execCommand", this.command, this.commandArg)
  1569. );
  1570. }else{
  1571. // hide button because editor doesn't support command (due to browser limitations)
  1572. this.button.domNode.style.display = "none";
  1573. }
  1574. }
  1575. this.connect(this.editor, "onNormalizedDisplayChanged", "updateState");
  1576. },
  1577. setToolbar: function(/*dijit.Toolbar*/ toolbar){
  1578. // summary:
  1579. // Tell the plugin to add it's controller widget (often a button)
  1580. // to the toolbar. Does nothing if there is no controller widget.
  1581. // TODO: refactor code to just pass toolbar to constructor.
  1582. if(this.button){
  1583. toolbar.addChild(this.button);
  1584. }
  1585. // console.debug("adding", this.button, "to:", toolbar);
  1586. },
  1587. set: function(/* attribute */ name, /* anything */ value){
  1588. // summary:
  1589. // Set a property on a plugin
  1590. // name:
  1591. // The property to set.
  1592. // value:
  1593. // The value to set in the property.
  1594. // description:
  1595. // Sets named properties on a plugin which may potentially be handled by a
  1596. // setter in the plugin.
  1597. // For example, if the plugin has a properties "foo"
  1598. // and "bar" and a method named "_setFooAttr", calling:
  1599. // | plugin.set("foo", "Howdy!");
  1600. // would be equivalent to writing:
  1601. // | plugin._setFooAttr("Howdy!");
  1602. // and:
  1603. // | plugin.set("bar", 3);
  1604. // would be equivalent to writing:
  1605. // | plugin.bar = 3;
  1606. //
  1607. // set() may also be called with a hash of name/value pairs, ex:
  1608. // | plugin.set({
  1609. // | foo: "Howdy",
  1610. // | bar: 3
  1611. // | })
  1612. // This is equivalent to calling set(foo, "Howdy") and set(bar, 3)
  1613. if(typeof name === "object"){
  1614. for(var x in name){
  1615. this.set(x, name[x]);
  1616. }
  1617. return this;
  1618. }
  1619. var names = this._getAttrNames(name);
  1620. if(this[names.s]){
  1621. // use the explicit setter
  1622. var result = this[names.s].apply(this, Array.prototype.slice.call(arguments, 1));
  1623. }else{
  1624. this._set(name, value);
  1625. }
  1626. return result || this;
  1627. },
  1628. get: function(name){
  1629. // summary:
  1630. // Get a property from a plugin.
  1631. // name:
  1632. // The property to get.
  1633. // description:
  1634. // Get a named property from a plugin. The property may
  1635. // potentially be retrieved via a getter method. If no getter is defined, this
  1636. // just retrieves the object's property.
  1637. // For example, if the plugin has a properties "foo"
  1638. // and "bar" and a method named "_getFooAttr", calling:
  1639. // | plugin.get("foo");
  1640. // would be equivalent to writing:
  1641. // | plugin._getFooAttr();
  1642. // and:
  1643. // | plugin.get("bar");
  1644. // would be equivalent to writing:
  1645. // | plugin.bar;
  1646. var names = this._getAttrNames(name);
  1647. return this[names.g] ? this[names.g]() : this[name];
  1648. },
  1649. _setDisabledAttr: function(disabled){
  1650. // summary:
  1651. // Function to set the plugin state and call updateState to make sure the
  1652. // button is updated appropriately.
  1653. this.disabled = disabled;
  1654. this.updateState();
  1655. },
  1656. _getAttrNames: function(name){
  1657. // summary:
  1658. // Helper function for get() and set().
  1659. // Caches attribute name values so we don't do the string ops every time.
  1660. // tags:
  1661. // private
  1662. var apn = this._attrPairNames;
  1663. if(apn[name]){ return apn[name]; }
  1664. var uc = name.charAt(0).toUpperCase() + name.substr(1);
  1665. return (apn[name] = {
  1666. s: "_set"+uc+"Attr",
  1667. g: "_get"+uc+"Attr"
  1668. });
  1669. },
  1670. _set: function(/*String*/ name, /*anything*/ value){
  1671. // summary:
  1672. // Helper function to set new value for specified attribute
  1673. this[name] = value;
  1674. }
  1675. });
  1676. // Hash mapping plugin name to factory, used for registering plugins
  1677. _Plugin.registry = {};
  1678. return _Plugin;
  1679. });
  1680. },
  1681. 'dijit/_editor/plugins/FontChoice':function(){
  1682. define("dijit/_editor/plugins/FontChoice", [
  1683. "dojo/_base/array", // array.indexOf array.map
  1684. "dojo/_base/declare", // declare
  1685. "dojo/dom-construct", // domConstruct.place
  1686. "dojo/i18n", // i18n.getLocalization
  1687. "dojo/_base/lang", // lang.delegate lang.hitch lang.isString
  1688. "dojo/store/Memory", // MemoryStore
  1689. "dojo/_base/window", // win.withGlobal
  1690. "../../registry", // registry.getUniqueId
  1691. "../../_Widget",
  1692. "../../_TemplatedMixin",
  1693. "../../_WidgetsInTemplateMixin",
  1694. "../../form/FilteringSelect",
  1695. "../_Plugin",
  1696. "../range",
  1697. "../selection",
  1698. "dojo/i18n!../nls/FontChoice"
  1699. ], function(array, declare, domConstruct, i18n, lang, MemoryStore, win,
  1700. registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi, selectionapi){
  1701. /*=====
  1702. var _Plugin = dijit._editor._Plugin;
  1703. var _Widget = dijit._Widget;
  1704. var _TemplatedMixin = dijit._TemplatedMixin;
  1705. var _WidgetsInTemplateMixin = dijit._WidgetsInTemplateMixin;
  1706. var FilteringSelect = dijit.form.FilteringSelect;
  1707. =====*/
  1708. // module:
  1709. // dijit/_editor/plugins/FontChoice
  1710. // summary:
  1711. // fontchoice, fontsize, and formatblock editor plugins
  1712. var _FontDropDown = declare("dijit._editor.plugins._FontDropDown",
  1713. [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
  1714. // summary:
  1715. // Base class for widgets that contains a label (like "Font:")
  1716. // and a FilteringSelect drop down to pick a value.
  1717. // Used as Toolbar entry.
  1718. // label: [public] String
  1719. // The label to apply to this particular FontDropDown.
  1720. label: "",
  1721. // plainText: [public] boolean
  1722. // Flag to indicate that the returned label should be plain text
  1723. // instead of an example.
  1724. plainText: false,
  1725. // templateString: [public] String
  1726. // The template used to construct the labeled dropdown.
  1727. templateString:
  1728. "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" +
  1729. "<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" +
  1730. "<input data-dojo-type='dijit.form.FilteringSelect' required='false' " +
  1731. "data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " +
  1732. "tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" +
  1733. "</span>",
  1734. postMixInProperties: function(){
  1735. // summary:
  1736. // Over-ride to set specific properties.
  1737. this.inherited(arguments);
  1738. this.strings = i18n.getLocalization("dijit._editor", "FontChoice");
  1739. // Set some substitution variables used in the template
  1740. this.label = this.strings[this.command];
  1741. this.id = registry.getUniqueId(this.declaredClass.replace(/\./g,"_")); // TODO: unneeded??
  1742. this.selectId = this.id + "_select"; // used in template
  1743. this.inherited(arguments);
  1744. },
  1745. postCreate: function(){
  1746. // summary:
  1747. // Over-ride for the default postCreate action
  1748. // This establishes the filtering selects and the like.
  1749. // Initialize the list of items in the drop down by creating data store with items like:
  1750. // {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" }
  1751. this.select.set("store", new MemoryStore({
  1752. idProperty: "value",
  1753. data: array.map(this.values, function(value){
  1754. var name = this.strings[value] || value;
  1755. return {
  1756. label: this.getLabel(value, name),
  1757. name: name,
  1758. value: value
  1759. };
  1760. }, this)
  1761. }));
  1762. this.select.set("value", "", false);
  1763. this.disabled = this.select.get("disabled");
  1764. },
  1765. _setValueAttr: function(value, priorityChange){
  1766. // summary:
  1767. // Over-ride for the default action of setting the
  1768. // widget value, maps the input to known values
  1769. // value: Object|String
  1770. // The value to set in the select.
  1771. // priorityChange:
  1772. // Optional parameter used to tell the select whether or not to fire
  1773. // onChange event.
  1774. // if the value is not a permitted value, just set empty string to prevent showing the warning icon
  1775. priorityChange = priorityChange !== false;
  1776. this.select.set('value', array.indexOf(this.values,value) < 0 ? "" : value, priorityChange);
  1777. if(!priorityChange){
  1778. // Clear the last state in case of updateState calls. Ref: #10466
  1779. this.select._lastValueReported=null;
  1780. }
  1781. },
  1782. _getValueAttr: function(){
  1783. // summary:
  1784. // Allow retrieving the value from the composite select on
  1785. // call to button.get("value");
  1786. return this.select.get('value');
  1787. },
  1788. focus: function(){
  1789. // summary:
  1790. // Over-ride for focus control of this widget. Delegates focus down to the
  1791. // filtering select.
  1792. this.select.focus();
  1793. },
  1794. _setDisabledAttr: function(value){
  1795. // summary:
  1796. // Over-ride for the button's 'disabled' attribute so that it can be
  1797. // disabled programmatically.
  1798. // Save off ths disabled state so the get retrieves it correctly
  1799. //without needing to have a function proxy it.
  1800. this.disabled = value;
  1801. this.select.set("disabled", value);
  1802. }
  1803. });
  1804. var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, {
  1805. // summary:
  1806. // Dropdown to select a font; goes in editor toolbar.
  1807. // generic: Boolean
  1808. // Use generic (web standard) font names
  1809. generic: false,
  1810. // command: [public] String
  1811. // The editor 'command' implemented by this plugin.
  1812. command: "fontName",
  1813. postMixInProperties: function(){
  1814. // summary:
  1815. // Over-ride for the default posr mixin control
  1816. if(!this.values){
  1817. this.values = this.generic ?
  1818. ["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics
  1819. ["Arial", "Times New Roman", "Comic Sans MS", "Courier New"];
  1820. }
  1821. this.inherited(arguments);
  1822. },
  1823. getLabel: function(value, name){
  1824. // summary:
  1825. // Function used to generate the labels of the format dropdown
  1826. // will return a formatted, or plain label based on the value
  1827. // of the plainText option.
  1828. // value: String
  1829. // The 'insert value' associated with a name
  1830. // name: String
  1831. // The text name of the value
  1832. if(this.plainText){
  1833. return name;
  1834. }else{
  1835. return "<div style='font-family: "+value+"'>" + name + "</div>";
  1836. }
  1837. },
  1838. _setValueAttr: function(value, priorityChange){
  1839. // summary:
  1840. // Over-ride for the default action of setting the
  1841. // widget value, maps the input to known values
  1842. priorityChange = priorityChange !== false;
  1843. if(this.generic){
  1844. var map = {
  1845. "Arial": "sans-serif",
  1846. "Helvetica": "sans-serif",
  1847. "Myriad": "sans-serif",
  1848. "Times": "serif",
  1849. "Times New Roman": "serif",
  1850. "Comic Sans MS": "cursive",
  1851. "Apple Chancery": "cursive",
  1852. "Courier": "monospace",
  1853. "Courier New": "monospace",
  1854. "Papyrus": "fantasy",
  1855. "Estrangelo Edessa": "cursive", // Windows 7
  1856. "Gabriola": "fantasy" // Windows 7
  1857. };
  1858. value = map[value] || value;
  1859. }
  1860. this.inherited(arguments, [value, priorityChange]);
  1861. }
  1862. });
  1863. var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, {
  1864. // summary:
  1865. // Dropdown to select a font size; goes in editor toolbar.
  1866. // command: [public] String
  1867. // The editor 'command' implemented by this plugin.
  1868. command: "fontSize",
  1869. // values: [public] Number[]
  1870. // The HTML font size values supported by this plugin
  1871. values: [1,2,3,4,5,6,7], // sizes according to the old HTML FONT SIZE
  1872. getLabel: function(value, name){
  1873. // summary:
  1874. // Function used to generate the labels of the format dropdown
  1875. // will return a formatted, or plain label based on the value
  1876. // of the plainText option.
  1877. // We're stuck using the deprecated FONT tag to correspond
  1878. // with the size measurements used by the editor
  1879. // value: String
  1880. // The 'insert value' associated with a name
  1881. // name: String
  1882. // The text name of the value
  1883. if(this.plainText){
  1884. return name;
  1885. }else{
  1886. return "<font size=" + value + "'>" + name + "</font>";
  1887. }
  1888. },
  1889. _setValueAttr: function(value, priorityChange){
  1890. // summary:
  1891. // Over-ride for the default action of setting the
  1892. // widget value, maps the input to known values
  1893. priorityChange = priorityChange !== false;
  1894. if(value.indexOf && value.indexOf("px") != -1){
  1895. var pixels = parseInt(value, 10);
  1896. value = {10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}[pixels] || value;
  1897. }
  1898. this.inherited(arguments, [value, priorityChange]);
  1899. }
  1900. });
  1901. var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, {
  1902. // summary:
  1903. // Dropdown to select a format (like paragraph or heading); goes in editor toolbar.
  1904. // command: [public] String
  1905. // The editor 'command' implemented by this plugin.
  1906. command: "formatBlock",
  1907. // values: [public] Array
  1908. // The HTML format tags supported by this plugin
  1909. values: ["noFormat", "p", "h1", "h2", "h3", "pre"],
  1910. postCreate: function(){
  1911. // Init and set the default value to no formatting. Update state will adjust it
  1912. // as needed.
  1913. this.inherited(arguments);
  1914. this.set("value", "noFormat", false);
  1915. },
  1916. getLabel: function(value, name){
  1917. // summary:
  1918. // Function used to generate the labels of the format dropdown
  1919. // will return a formatted, or plain label based on the value
  1920. // of the plainText option.
  1921. // value: String
  1922. // The 'insert value' associated with a name
  1923. // name: String
  1924. // The text name of the value
  1925. if(this.plainText || value == "noFormat"){
  1926. return name;
  1927. }else{
  1928. return "<" + value + ">" + name + "</" + value + ">";
  1929. }
  1930. },
  1931. _execCommand: function(editor, command, choice){
  1932. // summary:
  1933. // Over-ride for default exec-command label.
  1934. // Allows us to treat 'none' as special.
  1935. if(choice === "noFormat"){
  1936. var start;
  1937. var end;
  1938. var sel = rangeapi.getSelection(editor.window);
  1939. if(sel && sel.rangeCount > 0){
  1940. var range = sel.getRangeAt(0);
  1941. var node, tag;
  1942. if(range){
  1943. start = range.startContainer;
  1944. end = range.endContainer;
  1945. // find containing nodes of start/end.
  1946. while(start && start !== editor.editNode &&
  1947. start !== editor.document.body &&
  1948. start.nodeType !== 1){
  1949. start = start.parentNode;
  1950. }
  1951. while(end && end !== editor.editNode &&
  1952. end !== editor.document.body &&
  1953. end.nodeType !== 1){
  1954. end = end.parentNode;
  1955. }
  1956. var processChildren = lang.hitch(this, function(node, ary){
  1957. if(node.childNodes && node.childNodes.length){
  1958. var i;
  1959. for(i = 0; i < node.childNodes.length; i++){
  1960. var c = node.childNodes[i];
  1961. if(c.nodeType == 1){
  1962. if(win.withGlobal(editor.window, "inSelection", selectionapi, [c])){
  1963. var tag = c.tagName? c.tagName.toLowerCase(): "";
  1964. if(array.indexOf(this.values, tag) !== -1){
  1965. ary.push(c);
  1966. }
  1967. processChildren(c, ary);
  1968. }
  1969. }
  1970. }
  1971. }
  1972. });
  1973. var unformatNodes = lang.hitch(this, function(nodes){
  1974. // summary:
  1975. // Internal function to clear format nodes.
  1976. // nodes:
  1977. // The array of nodes to strip formatting from.
  1978. if(nodes && nodes.length){
  1979. editor.beginEditing();
  1980. while(nodes.length){
  1981. this._removeFormat(editor, nodes.pop());
  1982. }
  1983. editor.endEditing();
  1984. }
  1985. });
  1986. var clearNodes = [];
  1987. if(start == end){
  1988. //Contained within the same block, may be collapsed, but who cares, see if we
  1989. // have a block element to remove.
  1990. var block;
  1991. node = start;
  1992. while(node && node !== editor.editNode && node !== editor.document.body){
  1993. if(node.nodeType == 1){
  1994. tag = node.tagName? node.tagName.toLowerCase(): "";
  1995. if(array.indexOf(this.values, tag) !== -1){
  1996. block = node;
  1997. break;
  1998. }
  1999. }
  2000. node = node.parentNode;
  2001. }
  2002. //Also look for all child nodes in the selection that may need to be
  2003. //cleared of formatting
  2004. processChildren(start, clearNodes);
  2005. if(block){ clearNodes = [block].concat(clearNodes); }
  2006. unformatNodes(clearNodes);
  2007. }else{
  2008. // Probably a multi select, so we have to process it. Whee.
  2009. node = start;
  2010. while(win.withGlobal(editor.window, "inSelection", selectionapi, [node])){
  2011. if(node.nodeType == 1){
  2012. tag = node.tagName? node.tagName.toLowerCase(): "";
  2013. if(array.indexOf(this.values, tag) !== -1){
  2014. clearNodes.push(node);
  2015. }
  2016. processChildren(node,clearNodes);
  2017. }
  2018. node = node.nextSibling;
  2019. }
  2020. unformatNodes(clearNodes);
  2021. }
  2022. editor.onDisplayChanged();
  2023. }
  2024. }
  2025. }else{
  2026. editor.execCommand(command, choice);
  2027. }
  2028. },
  2029. _removeFormat: function(editor, node){
  2030. // summary:
  2031. // function to remove the block format node.
  2032. // node:
  2033. // The block format node to remove (and leave the contents behind)
  2034. if(editor.customUndo){
  2035. // So of course IE doesn't work right with paste-overs.
  2036. // We have to do this manually, which is okay since IE already uses
  2037. // customUndo and we turned it on for WebKit. WebKit pasted funny,
  2038. // so couldn't use the execCommand approach
  2039. while(node.firstChild){
  2040. domConstruct.place(node.firstChild, node, "before");
  2041. }
  2042. node.parentNode.removeChild(node);
  2043. }else{
  2044. // Everyone else works fine this way, a paste-over and is native
  2045. // undo friendly.
  2046. win.withGlobal(editor.window,
  2047. "selectElementChildren", selectionapi, [node]);
  2048. var html = win.withGlobal(editor.window,
  2049. "getSelectedHtml", selectionapi, [null]);
  2050. win.withGlobal(editor.window,
  2051. "selectElement", selectionapi, [node]);
  2052. editor.execCommand("inserthtml", html||"");
  2053. }
  2054. }
  2055. });
  2056. // TODO: for 2.0, split into FontChoice plugin into three separate classes,
  2057. // one for each command (and change registry below)
  2058. var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin,{
  2059. // summary:
  2060. // This plugin provides three drop downs for setting style in the editor
  2061. // (font, font size, and format block), as controlled by command.
  2062. //
  2063. // description:
  2064. // The commands provided by this plugin are:
  2065. //
  2066. // * fontName
  2067. // | Provides a drop down to select from a list of font names
  2068. // * fontSize
  2069. // | Provides a drop down to select from a list of font sizes
  2070. // * formatBlock
  2071. // | Provides a drop down to select from a list of block styles
  2072. // |
  2073. //
  2074. // which can easily be added to an editor by including one or more of the above commands
  2075. // in the `plugins` attribute as follows:
  2076. //
  2077. // | plugins="['fontName','fontSize',...]"
  2078. //
  2079. // It is possible to override the default dropdown list by providing an Array for the `custom` property when
  2080. // instantiating this plugin, e.g.
  2081. //
  2082. // | plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', custom:['Verdana','Myriad','Garamond']},...]"
  2083. //
  2084. // Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with
  2085. // [CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families)
  2086. //
  2087. // Note that the editor is often unable to properly handle font styling information defined outside
  2088. // the context of the current editor instance, such as pre-populated HTML.
  2089. // useDefaultCommand: [protected] Boolean
  2090. // Override _Plugin.useDefaultCommand...
  2091. // processing is handled by this plugin, not by dijit.Editor.
  2092. useDefaultCommand: false,
  2093. _initButton: function(){
  2094. // summary:
  2095. // Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar,
  2096. // rather than a simple button.
  2097. // tags:
  2098. // protected
  2099. // Create the widget to go into the toolbar (the so-called "button")
  2100. var clazz = {
  2101. fontName: _FontNameDropDown,
  2102. fontSize: _FontSizeDropDown,
  2103. formatBlock: _FormatBlockDropDown
  2104. }[this.command],
  2105. params = this.params;
  2106. // For back-compat reasons support setting custom values via "custom" parameter
  2107. // rather than "values" parameter
  2108. if(this.params.custom){
  2109. params.values = this.params.custom;
  2110. }
  2111. var editor = this.editor;
  2112. this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params));
  2113. // Reflect changes to the drop down in the editor
  2114. this.connect(this.button.select, "onChange", function(choice){
  2115. // User invoked change, since all internal updates set priorityChange to false and will
  2116. // not trigger an onChange event.
  2117. if(this.editor.focused){
  2118. // put focus back in the iframe, unless focus has somehow been shifted out of the editor completely
  2119. this.editor.focus();
  2120. }
  2121. if(this.command == "fontName" && choice.indexOf(" ") != -1){ choice = "'" + choice + "'"; }
  2122. // Invoke, the editor already normalizes commands called through its
  2123. // execCommand.
  2124. if(this.button._execCommand){
  2125. this.button._execCommand(this.editor, this.command, choice);
  2126. }else{
  2127. this.editor.execCommand(this.command, choice);
  2128. }
  2129. });
  2130. },
  2131. updateState: function(){
  2132. // summary:
  2133. // Overrides _Plugin.updateState(). This controls updating the menu
  2134. // options to the right values on state changes in the document (that trigger a
  2135. // test of the actions.)
  2136. // It set value of drop down in toolbar to reflect font/font size/format block
  2137. // of text at current caret position.
  2138. // tags:
  2139. // protected
  2140. var _e = this.editor;
  2141. var _c = this.command;
  2142. if(!_e || !_e.isLoaded || !_c.length){ return; }
  2143. if(this.button){
  2144. var disabled = this.get("disabled");
  2145. this.button.set("disabled", disabled);
  2146. if(disabled){ return; }
  2147. var value;
  2148. try{
  2149. value = _e.queryCommandValue(_c) || "";
  2150. }catch(e){
  2151. //Firefox may throw error above if the editor is just loaded, ignore it
  2152. value = "";
  2153. }
  2154. // strip off single quotes, if any
  2155. var quoted = lang.isString(value) && (value.match(/'([^']*)'/) || value.match(/"([^"]*)"/));
  2156. if(quoted){ value = quoted[1]; }
  2157. if(_c === "formatBlock"){
  2158. if(!value || value == "p"){
  2159. // Some browsers (WebKit) doesn't actually get the tag info right.
  2160. // and IE returns paragraph when in a DIV!, so incorrect a lot,
  2161. // so we have double-check it.
  2162. value = null;
  2163. var elem;
  2164. // Try to find the current element where the caret is.
  2165. var sel = rangeapi.getSelection(this.editor.window);
  2166. if(sel && sel.rangeCount > 0){
  2167. var range = sel.getRangeAt(0);
  2168. if(range){
  2169. elem = range.endContainer;
  2170. }
  2171. }
  2172. // Okay, now see if we can find one of the formatting types we're in.
  2173. while(elem && elem !== _e.editNode && elem !== _e.document){
  2174. var tg = elem.tagName?elem.tagName.toLowerCase():"";
  2175. if(tg && array.indexOf(this.button.values, tg) > -1){
  2176. value = tg;
  2177. break;
  2178. }
  2179. elem = elem.parentNode;
  2180. }
  2181. if(!value){
  2182. // Still no value, so lets select 'none'.
  2183. value = "noFormat";
  2184. }
  2185. }else{
  2186. // Check that the block format is one allowed, if not,
  2187. // null it so that it gets set to empty.
  2188. if(array.indexOf(this.button.values, value) < 0){
  2189. value = "noFormat";
  2190. }
  2191. }
  2192. }
  2193. if(value !== this.button.get("value")){
  2194. // Set the value, but denote it is not a priority change, so no
  2195. // onchange fires.
  2196. this.button.set('value', value, false);
  2197. }
  2198. }
  2199. }
  2200. });
  2201. // Register these plugins
  2202. array.forEach(["fontName", "fontSize", "formatBlock"], function(name){
  2203. _Plugin.registry[name] = function(args){
  2204. return new FontChoice({
  2205. command: name,
  2206. plainText: args.plainText
  2207. });
  2208. };
  2209. });
  2210. });
  2211. },
  2212. 'dijit/_editor/html':function(){
  2213. define("dijit/_editor/html", [
  2214. "dojo/_base/array",
  2215. "dojo/_base/lang", // lang.getObject
  2216. "dojo/_base/sniff", // has("ie")
  2217. ".." // for exporting symbols to dijit._editor (remove for 2.0)
  2218. ], function(array, lang, has, dijit){
  2219. // module:
  2220. // dijit/_editor/html
  2221. // summary:
  2222. // Utility functions used by editor
  2223. // Tests for DOMNode.attributes[] behavior:
  2224. // - dom-attributes-explicit - attributes[] only lists explicitly user specified attributes
  2225. // - dom-attributes-specified-flag (IE8) - need to check attr.specified flag to skip attributes user didn't specify
  2226. // - Otherwise, in IE6-7. attributes[] will list hundreds of values, so need to do outerHTML to get attrs instead.
  2227. var form = document.createElement("form");
  2228. has.add("dom-attributes-explicit", form.attributes.length == 0); // W3C
  2229. has.add("dom-attributes-specified-flag", form.attributes.length > 0 && form.attributes.length < 40); // IE8
  2230. lang.getObject("_editor", true, dijit);
  2231. dijit._editor.escapeXml=function(/*String*/str, /*Boolean?*/noSingleQuotes){
  2232. // summary:
  2233. // Adds escape sequences for special characters in XML: &<>"'
  2234. // Optionally skips escapes for single quotes
  2235. str = str.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
  2236. if(!noSingleQuotes){
  2237. str = str.replace(/'/gm, "&#39;");
  2238. }
  2239. return str; // string
  2240. };
  2241. dijit._editor.getNodeHtml=function(/* DomNode */node){
  2242. var output;
  2243. switch(node.nodeType){
  2244. case 1: //element node
  2245. var lName = node.nodeName.toLowerCase();
  2246. if(!lName || lName.charAt(0) == "/"){
  2247. // IE does some strange things with malformed HTML input, like
  2248. // treating a close tag </span> without an open tag <span>, as
  2249. // a new tag with tagName of /span. Corrupts output HTML, remove
  2250. // them. Other browsers don't prefix tags that way, so will
  2251. // never show up.
  2252. return "";
  2253. }
  2254. output = '<' + lName;
  2255. //store the list of attributes and sort it to have the
  2256. //attributes appear in the dictionary order
  2257. var attrarray = [], attrhash = {};
  2258. var attr;
  2259. if(has("dom-attributes-explicit") || has("dom-attributes-specified-flag")){
  2260. // IE8+ and all other browsers.
  2261. var i = 0;
  2262. while((attr = node.attributes[i++])){
  2263. // ignore all attributes starting with _dj which are
  2264. // internal temporary attributes used by the editor
  2265. var n = attr.name;
  2266. if(n.substr(0,3) !== '_dj' &&
  2267. (!has("dom-attributes-specified-flag") || attr.specified) &&
  2268. !(n in attrhash)){ // workaround repeated attributes bug in IE8 (LinkDialog test)
  2269. var v = attr.value;
  2270. if(n == 'src' || n == 'href'){
  2271. if(node.getAttribute('_djrealurl')){
  2272. v = node.getAttribute('_djrealurl');
  2273. }
  2274. }
  2275. if(has("ie") === 8 && n === "style"){
  2276. v = v.replace("HEIGHT:", "height:").replace("WIDTH:", "width:");
  2277. }
  2278. attrarray.push([n,v]);
  2279. attrhash[n] = v;
  2280. }
  2281. }
  2282. }else{
  2283. // IE6-7 code path
  2284. var clone = /^input$|^img$/i.test(node.nodeName) ? node : node.cloneNode(false);
  2285. var s = clone.outerHTML;
  2286. // Split up and manage the attrs via regexp
  2287. // similar to prettyPrint attr logic.
  2288. var rgxp_attrsMatch = /[\w-]+=("[^"]*"|'[^']*'|\S*)/gi
  2289. var attrSplit = s.match(rgxp_attrsMatch);
  2290. s = s.substr(0, s.indexOf('>'));
  2291. array.forEach(attrSplit, function(attr){
  2292. if(attr){
  2293. var idx = attr.indexOf("=");
  2294. if(idx > 0){
  2295. var key = attr.substring(0,idx);
  2296. if(key.substr(0,3) != '_dj'){
  2297. if(key == 'src' || key == 'href'){
  2298. if(node.getAttribute('_djrealurl')){
  2299. attrarray.push([key,node.getAttribute('_djrealurl')]);
  2300. return;
  2301. }
  2302. }
  2303. var val, match;
  2304. switch(key){
  2305. case 'style':
  2306. val = node.style.cssText.toLowerCase();
  2307. break;
  2308. case 'class':
  2309. val = node.className;
  2310. break;
  2311. case 'width':
  2312. if(lName === "img"){
  2313. // This somehow gets lost on IE for IMG tags and the like
  2314. // and we have to find it in outerHTML, known IE oddity.
  2315. match=/width=(\S+)/i.exec(s);
  2316. if(match){
  2317. val = match[1];
  2318. }
  2319. break;
  2320. }
  2321. case 'height':
  2322. if(lName === "img"){
  2323. // This somehow gets lost on IE for IMG tags and the like
  2324. // and we have to find it in outerHTML, known IE oddity.
  2325. match=/height=(\S+)/i.exec(s);
  2326. if(match){
  2327. val = match[1];
  2328. }
  2329. break;
  2330. }
  2331. default:
  2332. val = node.getAttribute(key);
  2333. }
  2334. if(val != null){
  2335. attrarray.push([key, val.toString()]);
  2336. }
  2337. }
  2338. }
  2339. }
  2340. }, this);
  2341. }
  2342. attrarray.sort(function(a,b){
  2343. return a[0] < b[0] ? -1 : (a[0] == b[0] ? 0 : 1);
  2344. });
  2345. var j = 0;
  2346. while((attr = attrarray[j++])){
  2347. output += ' ' + attr[0] + '="' +
  2348. (lang.isString(attr[1]) ? dijit._editor.escapeXml(attr[1], true) : attr[1]) + '"';
  2349. }
  2350. if(lName === "script"){
  2351. // Browsers handle script tags differently in how you get content,
  2352. // but innerHTML always seems to work, so insert its content that way
  2353. // Yes, it's bad to allow script tags in the editor code, but some people
  2354. // seem to want to do it, so we need to at least return them right.
  2355. // other plugins/filters can strip them.
  2356. output += '>' + node.innerHTML +'</' + lName + '>';
  2357. }else{
  2358. if(node.childNodes.length){
  2359. output += '>' + dijit._editor.getChildrenHtml(node)+'</' + lName +'>';
  2360. }else{
  2361. switch(lName){
  2362. case 'br':
  2363. case 'hr':
  2364. case 'img':
  2365. case 'input':
  2366. case 'base':
  2367. case 'meta':
  2368. case 'area':
  2369. case 'basefont':
  2370. // These should all be singly closed
  2371. output += ' />';
  2372. break;
  2373. default:
  2374. // Assume XML style separate closure for everything else.
  2375. output += '></' + lName + '>';
  2376. }
  2377. }
  2378. }
  2379. break;
  2380. case 4: // cdata
  2381. case 3: // text
  2382. // FIXME:
  2383. output = dijit._editor.escapeXml(node.nodeValue, true);
  2384. break;
  2385. case 8: //comment
  2386. // FIXME:
  2387. output = '<!--' + dijit._editor.escapeXml(node.nodeValue, true) + '-->';
  2388. break;
  2389. default:
  2390. output = "<!-- Element not recognized - Type: " + node.nodeType + " Name: " + node.nodeName + "-->";
  2391. }
  2392. return output;
  2393. };
  2394. dijit._editor.getChildrenHtml = function(/* DomNode */dom){
  2395. // summary:
  2396. // Returns the html content of a DomNode and children
  2397. var out = "";
  2398. if(!dom){ return out; }
  2399. var nodes = dom["childNodes"] || dom;
  2400. //IE issue.
  2401. //If we have an actual node we can check parent relationships on for IE,
  2402. //We should check, as IE sometimes builds invalid DOMS. If no parent, we can't check
  2403. //And should just process it and hope for the best.
  2404. var checkParent = !has("ie") || nodes !== dom;
  2405. var node, i = 0;
  2406. while((node = nodes[i++])){
  2407. //IE is broken. DOMs are supposed to be a tree. But in the case of malformed HTML, IE generates a graph
  2408. //meaning one node ends up with multiple references (multiple parents). This is totally wrong and invalid, but
  2409. //such is what it is. We have to keep track and check for this because otherise the source output HTML will have dups.
  2410. //No other browser generates a graph. Leave it to IE to break a fundamental DOM rule. So, we check the parent if we can
  2411. //If we can't, nothing more we can do other than walk it.
  2412. if(!checkParent || node.parentNode == dom){
  2413. out += dijit._editor.getNodeHtml(node);
  2414. }
  2415. }
  2416. return out; // String
  2417. };
  2418. return dijit._editor;
  2419. });
  2420. },
  2421. 'dijit/_editor/BuxRichText':function(){
  2422. define("dijit/_editor/BuxRichText", [
  2423. "dojo/_base/array", // array.forEach array.indexOf array.some
  2424. "dojo/_base/config", // config
  2425. "dojo/_base/declare", // declare
  2426. "dojo/_base/Deferred", // Deferred
  2427. "dojo/dom", // dom.byId
  2428. "dojo/dom-attr", // domAttr.set or get
  2429. "dojo/dom-class", // domClass.add domClass.remove
  2430. "dojo/dom-construct", // domConstruct.create domConstruct.destroy domConstruct.place
  2431. "dojo/dom-geometry", // domGeometry.position
  2432. "dojo/dom-style", // domStyle.getComputedStyle domStyle.set
  2433. "dojo/_base/event", // event.stop
  2434. "dojo/_base/kernel", // kernel.deprecated
  2435. "dojo/keys", // keys.BACKSPACE keys.TAB
  2436. "dojo/_base/lang", // lang.clone lang.hitch lang.isArray lang.isFunction lang.isString lang.trim
  2437. "dojo/on", // on()
  2438. "dojo/query", // query
  2439. "dojo/ready", // ready
  2440. "dojo/_base/sniff", // has("ie") has("mozilla") has("opera") has("safari") has("webkit")
  2441. "dojo/topic", // topic.publish() (publish)
  2442. "dojo/_base/unload", // unload
  2443. "dojo/_base/url", // url
  2444. "dojo/_base/window", // win.body win.doc.body.focus win.doc.createElement win.global.location win.withGlobal
  2445. "../_Widget",
  2446. "../_CssStateMixin",
  2447. "./selection",
  2448. "./range",
  2449. "./html",
  2450. "../focus",
  2451. ".." // dijit._scopeName
  2452. ], function(array, config, declare, Deferred, dom, domAttr, domClass, domConstruct, domGeometry, domStyle,
  2453. event, kernel, keys, lang, on, query, ready, has, topic, unload, _Url, win,
  2454. _Widget, _CssStateMixin, selectionapi, rangeapi, htmlapi, focus, dijit){
  2455. /*=====
  2456. var _Widget = dijit._Widget;
  2457. var _CssStateMixin = dijit._CssStateMixin;
  2458. =====*/
  2459. // module:
  2460. // dijit/_editor/BuxRichText
  2461. // summary:
  2462. // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic
  2463. // WYSIWYG editing features.
  2464. // if you want to allow for rich text saving with back/forward actions, you must add a text area to your page with
  2465. // the id==dijit._scopeName + "._editor.BuxRichText.value" (typically "dijit._editor.BuxRichText.value). For example,
  2466. // something like this will work:
  2467. //
  2468. // <textarea id="dijit._editor.BuxRichText.value" style="display:none;position:absolute;top:-100px;left:-100px;height:3px;width:3px;overflow:hidden;"></textarea>
  2469. //
  2470. var BuxRichText = declare("dijit._editor.BuxRichText", [_Widget, _CssStateMixin], {
  2471. // summary:
  2472. // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic
  2473. // WYSIWYG editing features.
  2474. //
  2475. // description:
  2476. // dijit._editor.BuxRichText is the core of dijit.Editor, which provides basic
  2477. // WYSIWYG editing features. It also encapsulates the differences
  2478. // of different js engines for various browsers. Do not use this widget
  2479. // with an HTML &lt;TEXTAREA&gt; tag, since the browser unescapes XML escape characters,
  2480. // like &lt;. This can have unexpected behavior and lead to security issues
  2481. // such as scripting attacks.
  2482. //
  2483. // tags:
  2484. // private
  2485. constructor: function(params){
  2486. // contentPreFilters: Function(String)[]
  2487. // Pre content filter function register array.
  2488. // these filters will be executed before the actual
  2489. // editing area gets the html content.
  2490. this.contentPreFilters = [];
  2491. // contentPostFilters: Function(String)[]
  2492. // post content filter function register array.
  2493. // These will be used on the resulting html
  2494. // from contentDomPostFilters. The resulting
  2495. // content is the final html (returned by getValue()).
  2496. this.contentPostFilters = [];
  2497. // contentDomPreFilters: Function(DomNode)[]
  2498. // Pre content dom filter function register array.
  2499. // These filters are applied after the result from
  2500. // contentPreFilters are set to the editing area.
  2501. this.contentDomPreFilters = [];
  2502. // contentDomPostFilters: Function(DomNode)[]
  2503. // Post content dom filter function register array.
  2504. // These filters are executed on the editing area dom.
  2505. // The result from these will be passed to contentPostFilters.
  2506. this.contentDomPostFilters = [];
  2507. // editingAreaStyleSheets: dojo._URL[]
  2508. // array to store all the stylesheets applied to the editing area
  2509. this.editingAreaStyleSheets = [];
  2510. // Make a copy of this.events before we start writing into it, otherwise we
  2511. // will modify the prototype which leads to bad things on pages w/multiple editors
  2512. this.events = [].concat(this.events);
  2513. this._keyHandlers = {};
  2514. if(params && lang.isString(params.value)){
  2515. this.value = params.value;
  2516. }
  2517. this.onLoadDeferred = new Deferred();
  2518. },
  2519. baseClass: "dijitEditor",
  2520. // inheritWidth: Boolean
  2521. // whether to inherit the parent's width or simply use 100%
  2522. inheritWidth: false,
  2523. // focusOnLoad: [deprecated] Boolean
  2524. // Focus into this widget when the page is loaded
  2525. focusOnLoad: false,
  2526. // name: String?
  2527. // Specifies the name of a (hidden) <textarea> node on the page that's used to save
  2528. // the editor content on page leave. Used to restore editor contents after navigating
  2529. // to a new page and then hitting the back button.
  2530. name: "",
  2531. // styleSheets: [const] String
  2532. // semicolon (";") separated list of css files for the editing area
  2533. styleSheets: "",
  2534. // height: String
  2535. // Set height to fix the editor at a specific height, with scrolling.
  2536. // By default, this is 300px. If you want to have the editor always
  2537. // resizes to accommodate the content, use AlwaysShowToolbar plugin
  2538. // and set height="". If this editor is used within a layout widget,
  2539. // set height="100%".
  2540. height: "300px",
  2541. // minHeight: String
  2542. // The minimum height that the editor should have.
  2543. minHeight: "1em",
  2544. // isClosed: [private] Boolean
  2545. isClosed: true,
  2546. // isLoaded: [private] Boolean
  2547. isLoaded: false,
  2548. // _SEPARATOR: [private] String
  2549. // Used to concat contents from multiple editors into a single string,
  2550. // so they can be saved into a single <textarea> node. See "name" attribute.
  2551. _SEPARATOR: "@@**%%__RICHTEXTBOUNDRY__%%**@@",
  2552. // _NAME_CONTENT_SEP: [private] String
  2553. // USed to separate name from content. Just a colon isn't safe.
  2554. _NAME_CONTENT_SEP: "@@**%%:%%**@@",
  2555. // onLoadDeferred: [readonly] dojo.Deferred
  2556. // Deferred which is fired when the editor finishes loading.
  2557. // Call myEditor.onLoadDeferred.then(callback) it to be informed
  2558. // when the rich-text area initialization is finalized.
  2559. onLoadDeferred: null,
  2560. // isTabIndent: Boolean
  2561. // Make tab key and shift-tab indent and outdent rather than navigating.
  2562. // Caution: sing this makes web pages inaccessible to users unable to use a mouse.
  2563. isTabIndent: false,
  2564. // disableSpellCheck: [const] Boolean
  2565. // When true, disables the browser's native spell checking, if supported.
  2566. // Works only in Firefox.
  2567. disableSpellCheck: false,
  2568. postCreate: function(){
  2569. if("textarea" === this.domNode.tagName.toLowerCase()){
  2570. console.warn("BuxRichText should not be used with the TEXTAREA tag. See dijit._editor.BuxRichText docs.");
  2571. }
  2572. // Push in the builtin filters now, making them the first executed, but not over-riding anything
  2573. // users passed in. See: #6062
  2574. this.contentPreFilters = [lang.hitch(this, "_preFixUrlAttributes")].concat(this.contentPreFilters);
  2575. if(has("mozilla")){
  2576. this.contentPreFilters = [this._normalizeFontStyle].concat(this.contentPreFilters);
  2577. this.contentPostFilters = [this._removeMozBogus].concat(this.contentPostFilters);
  2578. }
  2579. if(has("webkit")){
  2580. // Try to clean up WebKit bogus artifacts. The inserted classes
  2581. // made by WebKit sometimes messes things up.
  2582. this.contentPreFilters = [this._removeWebkitBogus].concat(this.contentPreFilters);
  2583. this.contentPostFilters = [this._removeWebkitBogus].concat(this.contentPostFilters);
  2584. }
  2585. if(has("ie") || has("trident")){
  2586. // IE generates <strong> and <em> but we want to normalize to <b> and <i>
  2587. // Still happens in IE11!
  2588. this.contentPostFilters = [this._normalizeFontStyle].concat(this.contentPostFilters);
  2589. this.contentDomPostFilters = [lang.hitch(this, this._stripBreakerNodes)].concat(this.contentDomPostFilters);
  2590. }
  2591. this.inherited(arguments);
  2592. topic.publish(dijit._scopeName + "._editor.BuxRichText::init", this);
  2593. this.open();
  2594. this.setupDefaultShortcuts();
  2595. },
  2596. setupDefaultShortcuts: function(){
  2597. // summary:
  2598. // Add some default key handlers
  2599. // description:
  2600. // Overwrite this to setup your own handlers. The default
  2601. // implementation does not use Editor commands, but directly
  2602. // executes the builtin commands within the underlying browser
  2603. // support.
  2604. // tags:
  2605. // protected
  2606. var exec = lang.hitch(this, function(cmd, arg){
  2607. return function(){
  2608. return !this.execCommand(cmd,arg);
  2609. };
  2610. });
  2611. var ctrlKeyHandlers = {
  2612. b: exec("bold"),
  2613. i: exec("italic"),
  2614. u: exec("underline"),
  2615. a: exec("selectall"),
  2616. s: function(){ this.save(true); },
  2617. m: function(){ this.isTabIndent = !this.isTabIndent; },
  2618. "1": exec("formatblock", "h1"),
  2619. "2": exec("formatblock", "h2"),
  2620. "3": exec("formatblock", "h3"),
  2621. "4": exec("formatblock", "h4"),
  2622. "\\": exec("insertunorderedlist")
  2623. };
  2624. if(!has("ie")){
  2625. ctrlKeyHandlers.Z = exec("redo"); //FIXME: undo?
  2626. }
  2627. var key;
  2628. for(key in ctrlKeyHandlers){
  2629. this.addKeyHandler(key, true, false, ctrlKeyHandlers[key]);
  2630. }
  2631. },
  2632. // events: [private] String[]
  2633. // events which should be connected to the underlying editing area
  2634. events: ["onKeyPress", "onKeyDown", "onKeyUp"], // onClick handled specially
  2635. // captureEvents: [deprecated] String[]
  2636. // Events which should be connected to the underlying editing
  2637. // area, events in this array will be addListener with
  2638. // capture=true.
  2639. // TODO: looking at the code I don't see any distinction between events and captureEvents,
  2640. // so get rid of this for 2.0 if not sooner
  2641. captureEvents: [],
  2642. _editorCommandsLocalized: false,
  2643. _localizeEditorCommands: function(){
  2644. // summary:
  2645. // When IE is running in a non-English locale, the API actually changes,
  2646. // so that we have to say (for example) danraku instead of p (for paragraph).
  2647. // Handle that here.
  2648. // tags:
  2649. // private
  2650. if(BuxRichText._editorCommandsLocalized){
  2651. // Use the already generate cache of mappings.
  2652. this._local2NativeFormatNames = BuxRichText._local2NativeFormatNames;
  2653. this._native2LocalFormatNames = BuxRichText._native2LocalFormatNames;
  2654. return;
  2655. }
  2656. BuxRichText._editorCommandsLocalized = true;
  2657. BuxRichText._local2NativeFormatNames = {};
  2658. BuxRichText._native2LocalFormatNames = {};
  2659. this._local2NativeFormatNames = BuxRichText._local2NativeFormatNames;
  2660. this._native2LocalFormatNames = BuxRichText._native2LocalFormatNames;
  2661. //in IE, names for blockformat is locale dependent, so we cache the values here
  2662. //put p after div, so if IE returns Normal, we show it as paragraph
  2663. //We can distinguish p and div if IE returns Normal, however, in order to detect that,
  2664. //we have to call this.document.selection.createRange().parentElement() or such, which
  2665. //could slow things down. Leave it as it is for now
  2666. var formats = ['div', 'p', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'address'];
  2667. var localhtml = "", format, i=0;
  2668. while((format=formats[i++])){
  2669. //append a <br> after each element to separate the elements more reliably
  2670. if(format.charAt(1) !== 'l'){
  2671. localhtml += "<"+format+"><span>content</span></"+format+"><br/>";
  2672. }else{
  2673. localhtml += "<"+format+"><li>content</li></"+format+"><br/>";
  2674. }
  2675. }
  2676. // queryCommandValue returns empty if we hide editNode, so move it out of screen temporary
  2677. // Also, IE9 does weird stuff unless we do it inside the editor iframe.
  2678. var style = { position: "absolute", top: "0px", zIndex: 10, opacity: 0.01 };
  2679. var div = domConstruct.create('div', {style: style, innerHTML: localhtml});
  2680. win.body().appendChild(div);
  2681. // IE9 has a timing issue with doing this right after setting
  2682. // the inner HTML, so put a delay in.
  2683. var inject = lang.hitch(this, function(){
  2684. var node = div.firstChild;
  2685. while(node){
  2686. try{
  2687. selectionapi.selectElement(node.firstChild);
  2688. var nativename = node.tagName.toLowerCase();
  2689. this._local2NativeFormatNames[nativename] = document.queryCommandValue("formatblock");
  2690. this._native2LocalFormatNames[this._local2NativeFormatNames[nativename]] = nativename;
  2691. node = node.nextSibling.nextSibling;
  2692. //console.log("Mapped: ", nativename, " to: ", this._local2NativeFormatNames[nativename]);
  2693. }catch(e){ /*Sqelch the occasional IE9 error */ }
  2694. }
  2695. div.parentNode.removeChild(div);
  2696. div.innerHTML = "";
  2697. });
  2698. setTimeout(inject, 0);
  2699. },
  2700. open: function(/*DomNode?*/ element){
  2701. // summary:
  2702. // Transforms the node referenced in this.domNode into a rich text editing
  2703. // node.
  2704. // description:
  2705. // Sets up the editing area asynchronously. This will result in
  2706. // the creation and replacement with an iframe.
  2707. // tags:
  2708. // private
  2709. if(!this.onLoadDeferred || this.onLoadDeferred.fired >= 0){
  2710. this.onLoadDeferred = new Deferred();
  2711. }
  2712. if(!this.isClosed){ this.close(); }
  2713. topic.publish(dijit._scopeName + "._editor.BuxRichText::open", this);
  2714. if(arguments.length === 1 && element.nodeName){ // else unchanged
  2715. this.domNode = element;
  2716. }
  2717. var dn = this.domNode;
  2718. // "html" will hold the innerHTML of the srcNodeRef and will be used to
  2719. // initialize the editor.
  2720. var html;
  2721. if(lang.isString(this.value)){
  2722. // Allow setting the editor content programmatically instead of
  2723. // relying on the initial content being contained within the target
  2724. // domNode.
  2725. html = this.value;
  2726. delete this.value;
  2727. dn.innerHTML = "";
  2728. }else if(dn.nodeName && dn.nodeName.toLowerCase() == "textarea"){
  2729. // if we were created from a textarea, then we need to create a
  2730. // new editing harness node.
  2731. var ta = (this.textarea = dn);
  2732. this.name = ta.name;
  2733. html = ta.value;
  2734. dn = this.domNode = win.doc.createElement("div");
  2735. dn.setAttribute('widgetId', this.id);
  2736. ta.removeAttribute('widgetId');
  2737. dn.cssText = ta.cssText;
  2738. dn.className += " " + ta.className;
  2739. domConstruct.place(dn, ta, "before");
  2740. var tmpFunc = lang.hitch(this, function(){
  2741. //some browsers refuse to submit display=none textarea, so
  2742. //move the textarea off screen instead
  2743. domStyle.set(ta, {
  2744. display: "block",
  2745. position: "absolute",
  2746. top: "-1000px"
  2747. });
  2748. if(has("ie")){ //nasty IE bug: abnormal formatting if overflow is not hidden
  2749. var s = ta.style;
  2750. this.__overflow = s.overflow;
  2751. s.overflow = "hidden";
  2752. }
  2753. });
  2754. if(has("ie")){
  2755. setTimeout(tmpFunc, 10);
  2756. }else{
  2757. tmpFunc();
  2758. }
  2759. if(ta.form){
  2760. var resetValue = ta.value;
  2761. this.reset = function(){
  2762. var current = this.getValue();
  2763. if(current !== resetValue){
  2764. this.replaceValue(resetValue);
  2765. }
  2766. };
  2767. on(ta.form, "submit", lang.hitch(this, function(){
  2768. // Copy value to the <textarea> so it gets submitted along with form.
  2769. // FIXME: should we be calling close() here instead?
  2770. domAttr.set(ta, 'disabled', this.disabled); // don't submit the value if disabled
  2771. ta.value = this.getValue();
  2772. }));
  2773. }
  2774. }else{
  2775. html = htmlapi.getChildrenHtml(dn);
  2776. dn.innerHTML = "";
  2777. }
  2778. this.value = html;
  2779. // If we're a list item we have to put in a blank line to force the
  2780. // bullet to nicely align at the top of text
  2781. if(dn.nodeName && dn.nodeName === "LI"){
  2782. dn.innerHTML = " <br>";
  2783. }
  2784. // Construct the editor div structure.
  2785. this.header = dn.ownerDocument.createElement("div");
  2786. dn.appendChild(this.header);
  2787. this.editingArea = dn.ownerDocument.createElement("div");
  2788. dn.appendChild(this.editingArea);
  2789. this.footer = dn.ownerDocument.createElement("div");
  2790. dn.appendChild(this.footer);
  2791. if(!this.name){
  2792. this.name = this.id + "_AUTOGEN";
  2793. }
  2794. // User has pressed back/forward button so we lost the text in the editor, but it's saved
  2795. // in a hidden <textarea> (which contains the data for all the editors on this page),
  2796. // so get editor value from there
  2797. if(this.name !== "" && (!config["useXDomain"] || config["allowXdRichTextSave"])){
  2798. var saveTextarea = dom.byId(dijit._scopeName + "._editor.BuxRichText.value");
  2799. if(saveTextarea && saveTextarea.value !== ""){
  2800. var datas = saveTextarea.value.split(this._SEPARATOR), i=0, dat;
  2801. while((dat=datas[i++])){
  2802. var data = dat.split(this._NAME_CONTENT_SEP);
  2803. if(data[0] === this.name){
  2804. html = data[1];
  2805. datas = datas.splice(i, 1);
  2806. saveTextarea.value = datas.join(this._SEPARATOR);
  2807. break;
  2808. }
  2809. }
  2810. }
  2811. if(!BuxRichText._globalSaveHandler){
  2812. BuxRichText._globalSaveHandler = {};
  2813. unload.addOnUnload(function(){
  2814. var id;
  2815. for(id in BuxRichText._globalSaveHandler){
  2816. var f = BuxRichText._globalSaveHandler[id];
  2817. if(lang.isFunction(f)){
  2818. f();
  2819. }
  2820. }
  2821. });
  2822. }
  2823. BuxRichText._globalSaveHandler[this.id] = lang.hitch(this, "_saveContent");
  2824. }
  2825. this.isClosed = false;
  2826. var ifr = (this.editorObject = this.iframe = win.doc.createElement('iframe'));
  2827. ifr.id = this.id+"_iframe";
  2828. ifr.style.border = "none";
  2829. ifr.style.width = "100%";
  2830. if(this._layoutMode){
  2831. // iframe should be 100% height, thus getting it's height from surrounding
  2832. // <div> (which has the correct height set by Editor)
  2833. ifr.style.height = "100%";
  2834. }else{
  2835. if(has("ie") >= 7){
  2836. if(this.height){
  2837. ifr.style.height = this.height;
  2838. }
  2839. if(this.minHeight){
  2840. ifr.style.minHeight = this.minHeight;
  2841. }
  2842. }else{
  2843. ifr.style.height = this.height ? this.height : this.minHeight;
  2844. }
  2845. }
  2846. ifr.frameBorder = 0;
  2847. ifr._loadFunc = lang.hitch( this, function(w){
  2848. this.window = w;
  2849. this.document = w.document;
  2850. if(has("ie")){
  2851. this._localizeEditorCommands();
  2852. }
  2853. // Do final setup and set initial contents of editor
  2854. this.onLoad(html);
  2855. });
  2856. // Attach iframe to document, and set the initial (blank) content.
  2857. var src = this._getIframeDocTxt().replace(/\\/g, "\\\\").replace(/'/g, "\\'"),
  2858. s;
  2859. // IE10 and earlier will throw an "Access is denied" error when attempting to access the parent frame if
  2860. // document.domain has been set, unless the child frame also has the same document.domain set. The child frame
  2861. // can only set document.domain while the document is being constructed using open/write/close; attempting to
  2862. // set it later results in a different "This method can't be used in this context" error. See #17529
  2863. if (has("ie") < 11) {
  2864. s = 'javascript:document.open();try{parent.window;}catch(e){document.domain="' + document.domain + '";}' +
  2865. 'document.write(\'' + src + '\');document.close()';
  2866. }
  2867. else {
  2868. s = "javascript: '" + src + "'";
  2869. }
  2870. if(has("ie") == 9){
  2871. // On IE9, attach to document before setting the content, to avoid problem w/iframe running in
  2872. // wrong security context, see #16633.
  2873. this.editingArea.appendChild(ifr);
  2874. ifr.src = s;
  2875. }else{
  2876. // For other browsers, set src first, especially for IE6/7 where attaching first gives a warning on
  2877. // https:// about "this page contains secure and insecure items, do you want to view both?"
  2878. ifr.setAttribute('src', s);
  2879. this.editingArea.appendChild(ifr);
  2880. }
  2881. if(has("safari") <= 4){
  2882. src = ifr.getAttribute("src");
  2883. if(!src || src.indexOf("javascript") === -1){
  2884. // Safari 4 and earlier sometimes act oddly
  2885. // So we have to set it again.
  2886. setTimeout(function(){ifr.setAttribute('src', s);},0);
  2887. }
  2888. }
  2889. // TODO: this is a guess at the default line-height, kinda works
  2890. if(dn.nodeName === "LI"){
  2891. dn.lastChild.style.marginTop = "-1.2em";
  2892. }
  2893. domClass.add(this.domNode, this.baseClass);
  2894. },
  2895. //static cache variables shared among all instance of this class
  2896. _local2NativeFormatNames: {},
  2897. _native2LocalFormatNames: {},
  2898. _getIframeDocTxt: function(){
  2899. // summary:
  2900. // Generates the boilerplate text of the document inside the iframe (ie, <html><head>...</head><body/></html>).
  2901. // Editor content (if not blank) should be added afterwards.
  2902. // tags:
  2903. // private
  2904. var _cs = domStyle.getComputedStyle(this.domNode);
  2905. // The contents inside of <body>. The real contents are set later via a call to setValue().
  2906. // In auto-expand mode, need a wrapper div for AlwaysShowToolbar plugin to correctly
  2907. // expand/contract the editor as the content changes.
  2908. var html = "<div id='dijitEditorBody'></div>";
  2909. var font = [ _cs.fontWeight, _cs.fontSize, _cs.fontFamily ].join(" ");
  2910. // line height is tricky - applying a units value will mess things up.
  2911. // if we can't get a non-units value, bail out.
  2912. var lineHeight = _cs.lineHeight;
  2913. if(lineHeight.indexOf("px") >= 0){
  2914. lineHeight = parseFloat(lineHeight)/parseFloat(_cs.fontSize);
  2915. // console.debug(lineHeight);
  2916. }else if(lineHeight.indexOf("em")>=0){
  2917. lineHeight = parseFloat(lineHeight);
  2918. }else{
  2919. // If we can't get a non-units value, just default
  2920. // it to the CSS spec default of 'normal'. Seems to
  2921. // work better, esp on IE, than '1.0'
  2922. lineHeight = "normal";
  2923. }
  2924. var userStyle = "";
  2925. var self = this;
  2926. this.style.replace(/(^|;)\s*(line-|font-?)[^;]+/ig, function(match){
  2927. match = match.replace(/^;/ig,"") + ';';
  2928. var s = match.split(":")[0];
  2929. if(s){
  2930. s = lang.trim(s);
  2931. s = s.toLowerCase();
  2932. var i;
  2933. var sC = "";
  2934. for(i = 0; i < s.length; i++){
  2935. var c = s.charAt(i);
  2936. switch(c){
  2937. case "-":
  2938. i++;
  2939. c = s.charAt(i).toUpperCase();
  2940. default:
  2941. sC += c;
  2942. }
  2943. }
  2944. domStyle.set(self.domNode, sC, "");
  2945. }
  2946. userStyle += match + ';';
  2947. });
  2948. // need to find any associated label element and update iframe document title
  2949. var label=query('label[for="'+this.id+'"]');
  2950. return [
  2951. this.isLeftToRight() ? "<html>\n<head>\n" : "<html dir='rtl'>\n<head>\n",
  2952. (has("mozilla") && label.length ? "<title>" + label[0].innerHTML + "</title>\n" : ""),
  2953. "<meta http-equiv='Content-Type' content='text/html'>\n",
  2954. "<style>\n",
  2955. "\tbody,html {\n",
  2956. "\t\tbackground:transparent;\n",
  2957. "\t\tpadding: 1px 0 0 0;\n",
  2958. "\t\tmargin: -1px 0 0 0;\n", // remove extraneous vertical scrollbar on safari and firefox
  2959. "\t}\n",
  2960. "\tbody,html,#dijitEditorBody { outline: none; }",
  2961. // Set <body> to expand to full size of editor, so clicking anywhere will work.
  2962. // Except in auto-expand mode, in which case the editor expands to the size of <body>.
  2963. // Also determine how scrollers should be applied. In autoexpand mode (height = "") no scrollers on y at all.
  2964. // But in fixed height mode we want both x/y scrollers.
  2965. // Scrollers go on <body> since it's been set to height: 100%.
  2966. "html { height: 100%; width: 100%; overflow: hidden; }\n", // scroll bar is on #dijitEditorBody, shouldn't be on <html>
  2967. this.height ? "\tbody,#dijitEditorBody { height: 100%; width: 100%; overflow: auto; }\n" :
  2968. "\tbody,#dijitEditorBody { min-height: " + this.minHeight + "; width: 100%; overflow-x: auto; overflow-y: hidden; }\n",
  2969. // TODO: left positioning will cause contents to disappear out of view
  2970. // if it gets too wide for the visible area
  2971. "\tbody{\n",
  2972. "\t\ttop:0px;\n",
  2973. "\t\tleft:0px;\n",
  2974. "\t\tright:0px;\n",
  2975. "\t\tfont:", font, ";\n",
  2976. ((this.height||has("opera")) ? "" : "\t\tposition: fixed;\n"),
  2977. "\t\tline-height:", lineHeight,";\n",
  2978. "\t}\n",
  2979. "\tp{ margin: 1em 0; }\n",
  2980. "\tli > ul:-moz-first-node, li > ol:-moz-first-node{ padding-top: 1.2em; }\n",
  2981. // Can't set min-height in IE>=9, it puts layout on li, which puts move/resize handles.
  2982. (has("ie") || has("trident") ? "" : "\tli{ min-height:1.2em; }\n"),
  2983. "</style>\n",
  2984. this._applyEditingAreaStyleSheets(),"\n",
  2985. "</head>\n<body ",
  2986. "</head>\n<body role='main' ",
  2987. // Onload handler fills in real editor content.
  2988. // On IE9, sometimes onload is called twice, and the first time frameElement is null (test_FullScreen.html)
  2989. "onload='frameElement && frameElement._loadFunc(window,document)' ",
  2990. "style='"+userStyle+"'>", html, "</body>\n</html>"
  2991. ].join(""); // String
  2992. },
  2993. _applyEditingAreaStyleSheets: function(){
  2994. // summary:
  2995. // apply the specified css files in styleSheets
  2996. // tags:
  2997. // private
  2998. var files = [];
  2999. if(this.styleSheets){
  3000. files = this.styleSheets.split(';');
  3001. this.styleSheets = '';
  3002. }
  3003. //empty this.editingAreaStyleSheets here, as it will be filled in addStyleSheet
  3004. files = files.concat(this.editingAreaStyleSheets);
  3005. this.editingAreaStyleSheets = [];
  3006. var text='', i=0, url;
  3007. while((url=files[i++])){
  3008. var abstring = (new _Url(win.global.location, url)).toString();
  3009. this.editingAreaStyleSheets.push(abstring);
  3010. text += '<link rel="stylesheet" type="text/css" href="'+abstring+'"/>';
  3011. }
  3012. return text;
  3013. },
  3014. addStyleSheet: function(/*dojo._Url*/ uri){
  3015. // summary:
  3016. // add an external stylesheet for the editing area
  3017. // uri:
  3018. // A dojo.uri.Uri pointing to the url of the external css file
  3019. var url=uri.toString();
  3020. //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
  3021. if(url.charAt(0) === '.' || (url.charAt(0) !== '/' && !uri.host)){
  3022. url = (new _Url(win.global.location, url)).toString();
  3023. }
  3024. if(array.indexOf(this.editingAreaStyleSheets, url) > -1){
  3025. // console.debug("dijit._editor.BuxRichText.addStyleSheet: Style sheet "+url+" is already applied");
  3026. return;
  3027. }
  3028. this.editingAreaStyleSheets.push(url);
  3029. this.onLoadDeferred.addCallback(lang.hitch(this, function(){
  3030. if(this.document.createStyleSheet){ //IE
  3031. this.document.createStyleSheet(url);
  3032. }else{ //other browser
  3033. var head = this.document.getElementsByTagName("head")[0];
  3034. var stylesheet = this.document.createElement("link");
  3035. stylesheet.rel="stylesheet";
  3036. stylesheet.type="text/css";
  3037. stylesheet.href=url;
  3038. head.appendChild(stylesheet);
  3039. }
  3040. }));
  3041. },
  3042. removeStyleSheet: function(/*dojo._Url*/ uri){
  3043. // summary:
  3044. // remove an external stylesheet for the editing area
  3045. var url=uri.toString();
  3046. //if uri is relative, then convert it to absolute so that it can be resolved correctly in iframe
  3047. if(url.charAt(0) === '.' || (url.charAt(0) !== '/' && !uri.host)){
  3048. url = (new _Url(win.global.location, url)).toString();
  3049. }
  3050. var index = array.indexOf(this.editingAreaStyleSheets, url);
  3051. if(index === -1){
  3052. // console.debug("dijit._editor.BuxRichText.removeStyleSheet: Style sheet "+url+" has not been applied");
  3053. return;
  3054. }
  3055. delete this.editingAreaStyleSheets[index];
  3056. win.withGlobal(this.window,'query', dojo, ['link:[href="'+url+'"]']).orphan();
  3057. },
  3058. // disabled: Boolean
  3059. // The editor is disabled; the text cannot be changed.
  3060. disabled: false,
  3061. _mozSettingProps: {'styleWithCSS':false},
  3062. _setDisabledAttr: function(/*Boolean*/ value){
  3063. value = !!value;
  3064. this._set("disabled", value);
  3065. if(!this.isLoaded){
  3066. return;
  3067. } // this method requires init to be complete
  3068. var preventIEfocus = has("ie") && (this.isLoaded || !this.focusOnLoad);
  3069. if(preventIEfocus){
  3070. this.editNode.unselectable = "on";
  3071. }
  3072. this.editNode.contentEditable = !value;
  3073. this.editNode.tabIndex = value ? "-1" : this.tabIndex;
  3074. if(preventIEfocus){
  3075. this.defer(function(){
  3076. if(this.editNode){ // guard in case widget destroyed before timeout
  3077. this.editNode.unselectable = "off";
  3078. }
  3079. });
  3080. }
  3081. if(has("mozilla") && !value && this._mozSettingProps){
  3082. var ps = this._mozSettingProps;
  3083. var n;
  3084. for(n in ps){
  3085. if(ps.hasOwnProperty(n)){
  3086. try{
  3087. this.document.execCommand(n, false, ps[n]);
  3088. }catch(e2){
  3089. }
  3090. }
  3091. }
  3092. }
  3093. this._disabledOK = true;
  3094. },
  3095. /* Event handlers
  3096. *****************/
  3097. onLoad: function(/*String*/ html){
  3098. // summary:
  3099. // Handler after the iframe finishes loading.
  3100. // html: String
  3101. // Editor contents should be set to this value
  3102. // tags:
  3103. // protected
  3104. // TODO: rename this to _onLoad, make empty public onLoad() method, deprecate/make protected onLoadDeferred handler?
  3105. if(!this.window.__registeredWindow){
  3106. this.window.__registeredWindow = true;
  3107. this._iframeRegHandle = focus.registerIframe(this.iframe);
  3108. }
  3109. // there's a wrapper div around the content, see _getIframeDocTxt().
  3110. this.editNode = this.document.body.firstChild;
  3111. var _this = this;
  3112. // Helper code so IE and FF skip over focusing on the <iframe> and just focus on the inner <div>.
  3113. // See #4996 IE wants to focus the BODY tag.
  3114. this.beforeIframeNode = domConstruct.place("<div tabIndex=-1></div>", this.iframe, "before");
  3115. this.afterIframeNode = domConstruct.place("<div tabIndex=-1></div>", this.iframe, "after");
  3116. this.iframe.onfocus = this.document.onfocus = function(){
  3117. _this.editNode.focus();
  3118. };
  3119. this.focusNode = this.editNode; // for InlineEditBox
  3120. var events = this.events.concat(this.captureEvents);
  3121. var ap = this.iframe ? this.document : this.editNode;
  3122. array.forEach(events, function(item){
  3123. this.connect(ap, item.toLowerCase(), item);
  3124. }, this);
  3125. this.connect(ap, "onmouseup", "onClick"); // mouseup in the margin does not generate an onclick event
  3126. if(has("ie")){ // IE contentEditable
  3127. this.connect(this.document, "onmousedown", "_onIEMouseDown"); // #4996 fix focus
  3128. // give the node Layout on IE
  3129. // TODO: this may no longer be needed, since we've reverted IE to using an iframe,
  3130. // not contentEditable. Removing it would also probably remove the need for creating
  3131. // the extra <div> in _getIframeDocTxt()
  3132. this.editNode.style.zoom = 1.0;
  3133. }else{
  3134. this.connect(this.document, "onmousedown", function(){
  3135. // Clear the moveToStart focus, as mouse
  3136. // down will set cursor point. Required to properly
  3137. // work with selection/position driven plugins and clicks in
  3138. // the window. refs: #10678
  3139. delete this._cursorToStart;
  3140. });
  3141. }
  3142. if(has("webkit")){
  3143. //WebKit sometimes doesn't fire right on selections, so the toolbar
  3144. //doesn't update right. Therefore, help it out a bit with an additional
  3145. //listener. A mouse up will typically indicate a display change, so fire this
  3146. //and get the toolbar to adapt. Reference: #9532
  3147. this._webkitListener = this.connect(this.document, "onmouseup", "onDisplayChanged");
  3148. this.connect(this.document, "onmousedown", function(e){
  3149. var t = e.target;
  3150. if(t && (t === this.document.body || t === this.document)){
  3151. // Since WebKit uses the inner DIV, we need to check and set position.
  3152. // See: #12024 as to why the change was made.
  3153. setTimeout(lang.hitch(this, "placeCursorAtEnd"), 0);
  3154. }
  3155. });
  3156. }
  3157. if(has("ie")){
  3158. // Try to make sure 'hidden' elements aren't visible in edit mode (like browsers other than IE
  3159. // do). See #9103
  3160. try{
  3161. this.document.execCommand('RespectVisibilityInDesign', true, null);
  3162. }catch(e){/* squelch */}
  3163. }
  3164. this.isLoaded = true;
  3165. this.set('disabled', this.disabled); // initialize content to editable (or not)
  3166. // Note that setValue() call will only work after isLoaded is set to true (above)
  3167. // Set up a function to allow delaying the setValue until a callback is fired
  3168. // This ensures extensions like dijit.Editor have a way to hold the value set
  3169. // until plugins load (and do things like register filters).
  3170. var setContent = lang.hitch(this, function(){
  3171. var copyValue = this.value;
  3172. this.setValue(html);
  3173. // Defect #247392 - We added try/catch block in order to handle exception "This deferred has already been resolved".
  3174. // This happens in Cognos Workspace (BUX), while changing tab's order on the dashdoard, which contain "Text Editor" widget(s).
  3175. try {
  3176. if(this.onLoadDeferred){
  3177. this.onLoadDeferred.callback(true);
  3178. }
  3179. } catch (err) {
  3180. // 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.
  3181. if (err.message === "This deferred has already been resolved") {
  3182. this.setValue(copyValue);
  3183. console.log("Caught exception: " + err.message);
  3184. } else {
  3185. throw err;
  3186. }
  3187. }
  3188. this.onDisplayChanged();
  3189. if(this.focusOnLoad){
  3190. // after the document loads, then set focus after updateInterval expires so that
  3191. // onNormalizedDisplayChanged has run to avoid input caret issues
  3192. ready(lang.hitch(this, function(){ setTimeout(lang.hitch(this, "focus"), this.updateInterval); }));
  3193. }
  3194. // Save off the initial content now
  3195. this.value = this.getValue(true);
  3196. });
  3197. if(this.setValueDeferred){
  3198. this.setValueDeferred.addCallback(setContent);
  3199. }else{
  3200. setContent();
  3201. }
  3202. },
  3203. onKeyDown: function(/* Event */ e){
  3204. // summary:
  3205. // Handler for onkeydown event
  3206. // tags:
  3207. // protected
  3208. // we need this event at the moment to get the events from control keys
  3209. // such as the backspace. It might be possible to add this to Dojo, so that
  3210. // keyPress events can be emulated by the keyDown and keyUp detection.
  3211. if(e.keyCode === keys.TAB && this.isTabIndent){
  3212. event.stop(e); //prevent tab from moving focus out of editor
  3213. // FIXME: this is a poor-man's indent/outdent. It would be
  3214. // better if it added 4 "&nbsp;" chars in an undoable way.
  3215. // Unfortunately pasteHTML does not prove to be undoable
  3216. if(this.queryCommandEnabled((e.shiftKey ? "outdent" : "indent"))){
  3217. this.execCommand((e.shiftKey ? "outdent" : "indent"));
  3218. }
  3219. }
  3220. // Make tab and shift-tab skip over the <iframe>, going from the nested <div> to the toolbar
  3221. // or next element after the editor. Needed on IE<9 and firefox.
  3222. if(e.keyCode == keys.TAB && !this.isTabIndent){
  3223. if(e.shiftKey && !e.ctrlKey && !e.altKey){
  3224. // focus the <iframe> so the browser will shift-tab away from it instead
  3225. this.beforeIframeNode.focus();
  3226. }else if(!e.shiftKey && !e.ctrlKey && !e.altKey){
  3227. // focus node after the <iframe> so the browser will tab away from it instead
  3228. this.afterIframeNode.focus();
  3229. }
  3230. }
  3231. if(has("ie") < 9 && e.keyCode === keys.BACKSPACE && this.document.selection.type === "Control"){
  3232. // IE has a bug where if a non-text object is selected in the editor,
  3233. // hitting backspace would act as if the browser's back button was
  3234. // clicked instead of deleting the object. see #1069
  3235. e.stopPropagation();
  3236. e.preventDefault();
  3237. this.execCommand("delete");
  3238. }
  3239. if(has("ff")){
  3240. if(e.keyCode === keys.PAGE_UP || e.keyCode === keys.PAGE_DOWN ){
  3241. if(this.editNode.clientHeight >= this.editNode.scrollHeight){
  3242. // Stop the event to prevent firefox from trapping the cursor when there is no scroll bar.
  3243. e.preventDefault();
  3244. }
  3245. }
  3246. }
  3247. return true;
  3248. },
  3249. onKeyUp: function(/*===== e =====*/){
  3250. // summary:
  3251. // Handler for onkeyup event
  3252. // tags:
  3253. // callback
  3254. },
  3255. setDisabled: function(/*Boolean*/ disabled){
  3256. // summary:
  3257. // Deprecated, use set('disabled', ...) instead.
  3258. // tags:
  3259. // deprecated
  3260. kernel.deprecated('dijit.Editor::setDisabled is deprecated','use dijit.Editor::attr("disabled",boolean) instead', 2.0);
  3261. this.set('disabled',disabled);
  3262. },
  3263. _setValueAttr: function(/*String*/ value){
  3264. // summary:
  3265. // Registers that attr("value", foo) should call setValue(foo)
  3266. this.setValue(value);
  3267. },
  3268. _setDisableSpellCheckAttr: function(/*Boolean*/ disabled){
  3269. if(this.document){
  3270. domAttr.set(this.document.body, "spellcheck", !disabled);
  3271. }else{
  3272. // try again after the editor is finished loading
  3273. this.onLoadDeferred.addCallback(lang.hitch(this, function(){
  3274. domAttr.set(this.document.body, "spellcheck", !disabled);
  3275. }));
  3276. }
  3277. this._set("disableSpellCheck", disabled);
  3278. },
  3279. onKeyPress: function(e){
  3280. // summary:
  3281. // Handle the various key events
  3282. // tags:
  3283. // protected
  3284. if(e.keyCode === keys.SHIFT ||
  3285. e.keyCode === keys.ALT ||
  3286. e.keyCode === keys.META ||
  3287. e.keyCode === keys.CTRL ||
  3288. (e.keyCode == keys.TAB && !this.isTabIndent && !e.ctrlKey && !e.altKey)){
  3289. return true;
  3290. }
  3291. var c = (e.keyChar && e.keyChar.toLowerCase()) || e.keyCode,
  3292. handlers = this._keyHandlers[c],
  3293. args = arguments;
  3294. if(handlers && !e.altKey){
  3295. array.some(handlers, function(h){
  3296. // treat meta- same as ctrl-, for benefit of mac users
  3297. if(!(h.shift ^ e.shiftKey) && !(h.ctrl ^ (e.ctrlKey||e.metaKey))){
  3298. if(!h.handler.apply(this, args)){
  3299. e.preventDefault();
  3300. }
  3301. return true;
  3302. }
  3303. }, this);
  3304. }
  3305. // function call after the character has been inserted
  3306. if(!this._onKeyHitch){
  3307. this._onKeyHitch = lang.hitch(this, "onKeyPressed");
  3308. }
  3309. setTimeout(this._onKeyHitch, 1);
  3310. return true;
  3311. },
  3312. addKeyHandler: function(/*String*/ key, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Function*/ handler){
  3313. // summary:
  3314. // Add a handler for a keyboard shortcut
  3315. // description:
  3316. // The key argument should be in lowercase if it is a letter character
  3317. // tags:
  3318. // protected
  3319. if(!lang.isArray(this._keyHandlers[key])){
  3320. this._keyHandlers[key] = [];
  3321. }
  3322. //TODO: would be nice to make this a hash instead of an array for quick lookups
  3323. this._keyHandlers[key].push({
  3324. shift: shift || false,
  3325. ctrl: ctrl || false,
  3326. handler: handler
  3327. });
  3328. },
  3329. onKeyPressed: function(){
  3330. // summary:
  3331. // Handler for after the user has pressed a key, and the display has been updated.
  3332. // (Runs on a timer so that it runs after the display is updated)
  3333. // tags:
  3334. // private
  3335. this.onDisplayChanged(/*e*/); // can't pass in e
  3336. },
  3337. onClick: function(/*Event*/ e){
  3338. // summary:
  3339. // Handler for when the user clicks.
  3340. // tags:
  3341. // private
  3342. // console.info('onClick',this._tryDesignModeOn);
  3343. this.onDisplayChanged(e);
  3344. },
  3345. _onIEMouseDown: function(){
  3346. // summary:
  3347. // IE only to prevent 2 clicks to focus
  3348. // tags:
  3349. // protected
  3350. if(!this.focused && !this.disabled){
  3351. this.focus();
  3352. }
  3353. },
  3354. _onBlur: function(e){
  3355. // summary:
  3356. // Called from focus manager when focus has moved away from this editor
  3357. // tags:
  3358. // protected
  3359. // console.info('_onBlur')
  3360. this.inherited(arguments);
  3361. var newValue = this.getValue(true);
  3362. if(newValue !== this.value){
  3363. this.onChange(newValue);
  3364. }
  3365. this._set("value", newValue);
  3366. },
  3367. _onFocus: function(/*Event*/ e){
  3368. // summary:
  3369. // Called from focus manager when focus has moved into this editor
  3370. // tags:
  3371. // protected
  3372. // console.info('_onFocus')
  3373. if(!this.disabled){
  3374. if(!this._disabledOK){
  3375. this.set('disabled', false);
  3376. }
  3377. this.inherited(arguments);
  3378. }
  3379. },
  3380. // TODO: remove in 2.0
  3381. blur: function(){
  3382. // summary:
  3383. // Remove focus from this instance.
  3384. // tags:
  3385. // deprecated
  3386. if(!has("ie") && this.window.document.documentElement && this.window.document.documentElement.focus){
  3387. this.window.document.documentElement.focus();
  3388. }else if(win.doc.body.focus){
  3389. win.doc.body.focus();
  3390. }
  3391. },
  3392. focus: function(){
  3393. // summary:
  3394. // Move focus to this editor
  3395. if(!this.isLoaded){
  3396. this.focusOnLoad = true;
  3397. return;
  3398. }
  3399. if(this._cursorToStart){
  3400. delete this._cursorToStart;
  3401. if(this.editNode.childNodes){
  3402. this.placeCursorAtStart(); // this calls focus() so return
  3403. return;
  3404. }
  3405. }
  3406. if(has("ie") < 9){
  3407. //this.editNode.focus(); -> causes IE to scroll always (strict and quirks mode) to the top the Iframe
  3408. // if we fire the event manually and let the browser handle the focusing, the latest
  3409. // cursor position is focused like in FF
  3410. this.iframe.fireEvent('onfocus', document.createEventObject()); // createEventObject/fireEvent only in IE < 11
  3411. }else{
  3412. // Firefox and chrome
  3413. this.editNode.focus();
  3414. }
  3415. },
  3416. // _lastUpdate: 0,
  3417. updateInterval: 200,
  3418. _updateTimer: null,
  3419. onDisplayChanged: function(/*Event*/ /*===== e =====*/){
  3420. // summary:
  3421. // This event will be fired every time the display context
  3422. // changes and the result needs to be reflected in the UI.
  3423. // description:
  3424. // If you don't want to have update too often,
  3425. // onNormalizedDisplayChanged should be used instead
  3426. // tags:
  3427. // private
  3428. // var _t=new Date();
  3429. if(this._updateTimer){
  3430. clearTimeout(this._updateTimer);
  3431. }
  3432. if(!this._updateHandler){
  3433. this._updateHandler = lang.hitch(this,"onNormalizedDisplayChanged");
  3434. }
  3435. this._updateTimer = setTimeout(this._updateHandler, this.updateInterval);
  3436. // Technically this should trigger a call to watch("value", ...) registered handlers,
  3437. // but getValue() is too slow to call on every keystroke so we don't.
  3438. },
  3439. onNormalizedDisplayChanged: function(){
  3440. // summary:
  3441. // This event is fired every updateInterval ms or more
  3442. // description:
  3443. // If something needs to happen immediately after a
  3444. // user change, please use onDisplayChanged instead.
  3445. // tags:
  3446. // private
  3447. delete this._updateTimer;
  3448. },
  3449. onChange: function(/*===== newContent =====*/){
  3450. // summary:
  3451. // This is fired if and only if the editor loses focus and
  3452. // the content is changed.
  3453. },
  3454. _normalizeCommand: function(/*String*/ cmd, /*Anything?*/argument){
  3455. // summary:
  3456. // Used as the advice function to map our
  3457. // normalized set of commands to those supported by the target
  3458. // browser.
  3459. // tags:
  3460. // private
  3461. var command = cmd.toLowerCase();
  3462. if(command === "formatblock"){
  3463. if(has("safari") && argument === undefined){ command = "heading"; }
  3464. }else if(command === "hilitecolor" && !has("mozilla")){
  3465. command = "backcolor";
  3466. }
  3467. return command;
  3468. },
  3469. _qcaCache: {},
  3470. queryCommandAvailable: function(/*String*/ command){
  3471. // summary:
  3472. // Tests whether a command is supported by the host. Clients
  3473. // SHOULD check whether a command is supported before attempting
  3474. // to use it, behaviour for unsupported commands is undefined.
  3475. // command:
  3476. // The command to test for
  3477. // tags:
  3478. // private
  3479. // memoizing version. See _queryCommandAvailable for computing version
  3480. var ca = this._qcaCache[command];
  3481. if(ca !== undefined){ return ca; }
  3482. return (this._qcaCache[command] = this._queryCommandAvailable(command));
  3483. },
  3484. _queryCommandAvailable: function(/*String*/ command){
  3485. // summary:
  3486. // See queryCommandAvailable().
  3487. // tags:
  3488. // private
  3489. var ie = 1;
  3490. var mozilla = 1 << 1;
  3491. var webkit = 1 << 2;
  3492. var opera = 1 << 3;
  3493. function isSupportedBy(browsers){
  3494. return {
  3495. ie: Boolean(browsers & ie),
  3496. mozilla: Boolean(browsers & mozilla),
  3497. webkit: Boolean(browsers & webkit),
  3498. opera: Boolean(browsers & opera)
  3499. };
  3500. }
  3501. var supportedBy = null;
  3502. switch(command.toLowerCase()){
  3503. case "bold": case "italic": case "underline":
  3504. case "subscript": case "superscript":
  3505. case "fontname": case "fontsize":
  3506. case "forecolor": case "hilitecolor":
  3507. case "justifycenter": case "justifyfull": case "justifyleft":
  3508. case "justifyright": case "delete": case "selectall": case "toggledir":
  3509. supportedBy = isSupportedBy(mozilla | ie | webkit | opera);
  3510. break;
  3511. case "createlink": case "unlink": case "removeformat":
  3512. case "inserthorizontalrule": case "insertimage":
  3513. case "insertorderedlist": case "insertunorderedlist":
  3514. case "indent": case "outdent": case "formatblock":
  3515. case "inserthtml": case "undo": case "redo": case "strikethrough": case "tabindent":
  3516. supportedBy = isSupportedBy(mozilla | ie | opera | webkit);
  3517. break;
  3518. case "blockdirltr": case "blockdirrtl":
  3519. case "dirltr": case "dirrtl":
  3520. case "inlinedirltr": case "inlinedirrtl":
  3521. supportedBy = isSupportedBy(ie);
  3522. break;
  3523. case "cut": case "copy": case "paste":
  3524. supportedBy = isSupportedBy( ie | mozilla | webkit);
  3525. break;
  3526. case "inserttable":
  3527. supportedBy = isSupportedBy(mozilla | ie);
  3528. break;
  3529. case "insertcell": case "insertcol": case "insertrow":
  3530. case "deletecells": case "deletecols": case "deleterows":
  3531. case "mergecells": case "splitcell":
  3532. supportedBy = isSupportedBy(ie | mozilla);
  3533. break;
  3534. default: return false;
  3535. }
  3536. return ((has("ie") || has("trident")) && supportedBy.ie) ||
  3537. (has("mozilla") && supportedBy.mozilla) ||
  3538. (has("webkit") && supportedBy.webkit) ||
  3539. (has("opera") && supportedBy.opera); // Boolean return true if the command is supported, false otherwise
  3540. },
  3541. execCommand: function(/*String*/ command, argument){
  3542. // summary:
  3543. // Executes a command in the Rich Text area
  3544. // command:
  3545. // The command to execute
  3546. // argument:
  3547. // An optional argument to the command
  3548. // tags:
  3549. // protected
  3550. var returnValue;
  3551. //focus() is required for IE to work
  3552. //In addition, focus() makes sure after the execution of
  3553. //the command, the editor receives the focus as expected
  3554. if(this.focused){
  3555. // put focus back in the iframe, unless focus has somehow been shifted out of the editor completely
  3556. this.focus();
  3557. }
  3558. command = this._normalizeCommand(command, argument);
  3559. if(argument !== undefined){
  3560. if(command === "heading"){
  3561. throw new Error("unimplemented");
  3562. }else if(command === "formatblock" && (has("ie") || has("trident"))){
  3563. argument = '<'+argument+'>';
  3564. }
  3565. }
  3566. //Check to see if we have any over-rides for commands, they will be functions on this
  3567. //widget of the form _commandImpl. If we don't, fall through to the basic native
  3568. //exec command of the browser.
  3569. var implFunc = "_" + command + "Impl";
  3570. if(this[implFunc]){
  3571. returnValue = this[implFunc](argument);
  3572. }else{
  3573. argument = arguments.length > 1 ? argument : null;
  3574. if(argument || command !== "createlink"){
  3575. returnValue = this.document.execCommand(command, false, argument);
  3576. }
  3577. }
  3578. this.onDisplayChanged();
  3579. return returnValue;
  3580. },
  3581. queryCommandEnabled: function(/*String*/ command){
  3582. // summary:
  3583. // Check whether a command is enabled or not.
  3584. // command:
  3585. // The command to execute
  3586. // tags:
  3587. // protected
  3588. if(this.disabled || !this._disabledOK){ return false; }
  3589. command = this._normalizeCommand(command);
  3590. //Check to see if we have any over-rides for commands, they will be functions on this
  3591. //widget of the form _commandEnabledImpl. If we don't, fall through to the basic native
  3592. //command of the browser.
  3593. var implFunc = "_" + command + "EnabledImpl";
  3594. if(this[implFunc]){
  3595. return this[implFunc](command);
  3596. }else{
  3597. return this._browserQueryCommandEnabled(command);
  3598. }
  3599. },
  3600. queryCommandState: function(command){
  3601. // summary:
  3602. // Check the state of a given command and returns true or false.
  3603. // tags:
  3604. // protected
  3605. if(this.disabled || !this._disabledOK){ return false; }
  3606. command = this._normalizeCommand(command);
  3607. try{
  3608. return this.document.queryCommandState(command);
  3609. }catch(e){
  3610. //Squelch, occurs if editor is hidden on FF 3 (and maybe others.)
  3611. return false;
  3612. }
  3613. },
  3614. queryCommandValue: function(command){
  3615. // summary:
  3616. // Check the value of a given command. This matters most for
  3617. // custom selections and complex values like font value setting.
  3618. // tags:
  3619. // protected
  3620. if(this.disabled || !this._disabledOK){ return false; }
  3621. var r;
  3622. command = this._normalizeCommand(command);
  3623. if((has("ie") || has("trident")) && command === "formatblock"){
  3624. r = this._native2LocalFormatNames[this.document.queryCommandValue(command)];
  3625. }else if(has("mozilla") && command === "hilitecolor"){
  3626. var oldValue;
  3627. try{
  3628. oldValue = this.document.queryCommandValue("styleWithCSS");
  3629. }catch(e){
  3630. oldValue = false;
  3631. }
  3632. this.document.execCommand("styleWithCSS", false, true);
  3633. r = this.document.queryCommandValue(command);
  3634. this.document.execCommand("styleWithCSS", false, oldValue);
  3635. }else{
  3636. r = this.document.queryCommandValue(command);
  3637. }
  3638. return r;
  3639. },
  3640. // Misc.
  3641. _sCall: function(name, args){
  3642. // summary:
  3643. // Run the named method of dijit._editor.selection over the
  3644. // current editor instance's window, with the passed args.
  3645. // tags:
  3646. // private
  3647. return win.withGlobal(this.window, name, selectionapi, args);
  3648. },
  3649. // FIXME: this is a TON of code duplication. Why?
  3650. placeCursorAtStart: function(){
  3651. // summary:
  3652. // Place the cursor at the start of the editing area.
  3653. // tags:
  3654. // private
  3655. this.focus();
  3656. //see comments in placeCursorAtEnd
  3657. var isvalid=false;
  3658. if(has("mozilla")){
  3659. // TODO: Is this branch even necessary?
  3660. var first=this.editNode.firstChild;
  3661. while(first){
  3662. if(first.nodeType === 3){
  3663. if(first.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
  3664. isvalid=true;
  3665. this._sCall("selectElement", [ first ]);
  3666. break;
  3667. }
  3668. }else if(first.nodeType === 1){
  3669. isvalid=true;
  3670. var tg = first.tagName ? first.tagName.toLowerCase() : "";
  3671. // Collapse before childless tags.
  3672. if(/br|input|img|base|meta|area|basefont|hr|link/.test(tg)){
  3673. this._sCall("selectElement", [ first ]);
  3674. }else{
  3675. // Collapse inside tags with children.
  3676. this._sCall("selectElementChildren", [ first ]);
  3677. }
  3678. break;
  3679. }
  3680. first = first.nextSibling;
  3681. }
  3682. }else{
  3683. isvalid=true;
  3684. this._sCall("selectElementChildren", [ this.editNode ]);
  3685. }
  3686. if(isvalid){
  3687. this._sCall("collapse", [ true ]);
  3688. }
  3689. },
  3690. placeCursorAtEnd: function(){
  3691. // summary:
  3692. // Place the cursor at the end of the editing area.
  3693. // tags:
  3694. // private
  3695. this.focus();
  3696. //In mozilla, if last child is not a text node, we have to use
  3697. // selectElementChildren on this.editNode.lastChild otherwise the
  3698. // cursor would be placed at the end of the closing tag of
  3699. //this.editNode.lastChild
  3700. var isvalid=false;
  3701. if(has("mozilla")){
  3702. var last=this.editNode.lastChild;
  3703. while(last){
  3704. if(last.nodeType === 3){
  3705. if(last.nodeValue.replace(/^\s+|\s+$/g, "").length>0){
  3706. isvalid=true;
  3707. this._sCall("selectElement", [ last ]);
  3708. break;
  3709. }
  3710. }else if(last.nodeType === 1){
  3711. isvalid=true;
  3712. if(last.lastChild){
  3713. this._sCall("selectElement", [ last.lastChild ]);
  3714. }else{
  3715. this._sCall("selectElement", [ last ]);
  3716. }
  3717. break;
  3718. }
  3719. last = last.previousSibling;
  3720. }
  3721. }else{
  3722. isvalid=true;
  3723. this._sCall("selectElementChildren", [ this.editNode ]);
  3724. }
  3725. if(isvalid){
  3726. this._sCall("collapse", [ false ]);
  3727. }
  3728. },
  3729. getValue: function(/*Boolean?*/ nonDestructive){
  3730. // summary:
  3731. // Return the current content of the editing area (post filters
  3732. // are applied). Users should call get('value') instead.
  3733. // nonDestructive:
  3734. // defaults to false. Should the post-filtering be run over a copy
  3735. // of the live DOM? Most users should pass "true" here unless they
  3736. // *really* know that none of the installed filters are going to
  3737. // mess up the editing session.
  3738. // tags:
  3739. // private
  3740. if(this.textarea){
  3741. if(this.isClosed || !this.isLoaded){
  3742. return this.textarea.value;
  3743. }
  3744. }
  3745. return this._postFilterContent(null, nonDestructive);
  3746. },
  3747. _getValueAttr: function(){
  3748. // summary:
  3749. // Hook to make attr("value") work
  3750. return this.getValue(true);
  3751. },
  3752. setValue: function(/*String*/ html){
  3753. // summary:
  3754. // This function sets the content. No undo history is preserved.
  3755. // Users should use set('value', ...) instead.
  3756. // tags:
  3757. // deprecated
  3758. // TODO: remove this and getValue() for 2.0, and move code to _setValueAttr()
  3759. if(!this.isLoaded){
  3760. // try again after the editor is finished loading
  3761. this.onLoadDeferred.addCallback(lang.hitch(this, function(){
  3762. this.setValue(html);
  3763. }));
  3764. return;
  3765. }
  3766. this._cursorToStart = true;
  3767. if(this.textarea && (this.isClosed || !this.isLoaded)){
  3768. this.textarea.value=html;
  3769. }else{
  3770. html = this._preFilterContent(html);
  3771. var node = this.isClosed ? this.domNode : this.editNode;
  3772. // Use &nbsp; to avoid webkit problems where editor is disabled until the user clicks it
  3773. if(!html && has("webkit")){
  3774. html = "&#160;"; // &nbsp;
  3775. }
  3776. node.innerHTML = html;
  3777. this._preDomFilterContent(node);
  3778. }
  3779. this.onDisplayChanged();
  3780. this._set("value", this.getValue(true));
  3781. },
  3782. replaceValue: function(/*String*/ html){
  3783. // summary:
  3784. // This function set the content while trying to maintain the undo stack
  3785. // (now only works fine with Moz, this is identical to setValue in all
  3786. // other browsers)
  3787. // tags:
  3788. // protected
  3789. if(this.isClosed){
  3790. this.setValue(html);
  3791. }else if(this.window && this.window.getSelection && !has("mozilla")){ // Safari
  3792. // look ma! it's a totally f'd browser!
  3793. this.setValue(html);
  3794. }else if(this.window && this.window.getSelection){ // Moz
  3795. html = this._preFilterContent(html);
  3796. this.execCommand("selectall");
  3797. this.execCommand("inserthtml", html);
  3798. this._preDomFilterContent(this.editNode);
  3799. }else if(this.document && this.document.selection){//IE
  3800. //In IE, when the first element is not a text node, say
  3801. //an <a> tag, when replacing the content of the editing
  3802. //area, the <a> tag will be around all the content
  3803. //so for now, use setValue for IE too
  3804. this.setValue(html);
  3805. }
  3806. this._set("value", this.getValue(true));
  3807. },
  3808. _preFilterContent: function(/*String*/ html){
  3809. // summary:
  3810. // Filter the input before setting the content of the editing
  3811. // area. DOM pre-filtering may happen after this
  3812. // string-based filtering takes place but as of 1.2, this is not
  3813. // guaranteed for operations such as the inserthtml command.
  3814. // tags:
  3815. // private
  3816. var ec = html;
  3817. array.forEach(this.contentPreFilters, function(ef){ if(ef){ ec = ef(ec); } });
  3818. return ec;
  3819. },
  3820. _preDomFilterContent: function(/*DomNode*/ dom){
  3821. // summary:
  3822. // filter the input's live DOM. All filter operations should be
  3823. // considered to be "live" and operating on the DOM that the user
  3824. // will be interacting with in their editing session.
  3825. // tags:
  3826. // private
  3827. dom = dom || this.editNode;
  3828. array.forEach(this.contentDomPreFilters, function(ef){
  3829. if(ef && lang.isFunction(ef)){
  3830. ef(dom);
  3831. }
  3832. }, this);
  3833. },
  3834. _postFilterContent: function(
  3835. /*DomNode|DomNode[]|String?*/ dom,
  3836. /*Boolean?*/ nonDestructive){
  3837. // summary:
  3838. // filter the output after getting the content of the editing area
  3839. //
  3840. // description:
  3841. // post-filtering allows plug-ins and users to specify any number
  3842. // of transforms over the editor's content, enabling many common
  3843. // use-cases such as transforming absolute to relative URLs (and
  3844. // vice-versa), ensuring conformance with a particular DTD, etc.
  3845. // The filters are registered in the contentDomPostFilters and
  3846. // contentPostFilters arrays. Each item in the
  3847. // contentDomPostFilters array is a function which takes a DOM
  3848. // Node or array of nodes as its only argument and returns the
  3849. // same. It is then passed down the chain for further filtering.
  3850. // The contentPostFilters array behaves the same way, except each
  3851. // member operates on strings. Together, the DOM and string-based
  3852. // filtering allow the full range of post-processing that should
  3853. // be necessaray to enable even the most agressive of post-editing
  3854. // conversions to take place.
  3855. //
  3856. // If nonDestructive is set to "true", the nodes are cloned before
  3857. // filtering proceeds to avoid potentially destructive transforms
  3858. // to the content which may still needed to be edited further.
  3859. // Once DOM filtering has taken place, the serialized version of
  3860. // the DOM which is passed is run through each of the
  3861. // contentPostFilters functions.
  3862. //
  3863. // dom:
  3864. // a node, set of nodes, which to filter using each of the current
  3865. // members of the contentDomPostFilters and contentPostFilters arrays.
  3866. //
  3867. // nonDestructive:
  3868. // defaults to "false". If true, ensures that filtering happens on
  3869. // a clone of the passed-in content and not the actual node
  3870. // itself.
  3871. //
  3872. // tags:
  3873. // private
  3874. var ec;
  3875. if(!lang.isString(dom)){
  3876. dom = dom || this.editNode;
  3877. if(this.contentDomPostFilters.length){
  3878. if(nonDestructive){
  3879. dom = lang.clone(dom);
  3880. }
  3881. array.forEach(this.contentDomPostFilters, function(ef){
  3882. dom = ef(dom);
  3883. });
  3884. }
  3885. ec = htmlapi.getChildrenHtml(dom);
  3886. }else{
  3887. ec = dom;
  3888. }
  3889. if(!lang.trim(ec.replace(/^\xA0\xA0*/, '').replace(/\xA0\xA0*$/, '')).length){
  3890. ec = "";
  3891. }
  3892. // if(has("ie")){
  3893. // //removing appended <P>&nbsp;</P> for IE
  3894. // ec = ec.replace(/(?:<p>&nbsp;</p>[\n\r]*)+$/i,"");
  3895. // }
  3896. array.forEach(this.contentPostFilters, function(ef){
  3897. ec = ef(ec);
  3898. });
  3899. return ec;
  3900. },
  3901. _saveContent: function(){
  3902. // summary:
  3903. // Saves the content in an onunload event if the editor has not been closed
  3904. // tags:
  3905. // private
  3906. var saveTextarea = dom.byId(dijit._scopeName + "._editor.BuxRichText.value");
  3907. if(saveTextarea){
  3908. if(saveTextarea.value){
  3909. saveTextarea.value += this._SEPARATOR;
  3910. }
  3911. saveTextarea.value += this.name + this._NAME_CONTENT_SEP + this.getValue(true);
  3912. }
  3913. },
  3914. escapeXml: function(/*String*/ str, /*Boolean*/ noSingleQuotes){
  3915. // summary:
  3916. // Adds escape sequences for special characters in XML.
  3917. // Optionally skips escapes for single quotes
  3918. // tags:
  3919. // private
  3920. str = str.replace(/&/gm, "&amp;").replace(/</gm, "&lt;").replace(/>/gm, "&gt;").replace(/"/gm, "&quot;");
  3921. if(!noSingleQuotes){
  3922. str = str.replace(/'/gm, "&#39;");
  3923. }
  3924. return str; // string
  3925. },
  3926. getNodeHtml: function(/* DomNode */ node){
  3927. // summary:
  3928. // Deprecated. Use dijit/_editor/html::_getNodeHtml() instead.
  3929. // tags:
  3930. // deprecated
  3931. kernel.deprecated('dijit.Editor::getNodeHtml is deprecated','use dijit/_editor/html::getNodeHtml instead', 2);
  3932. return htmlapi.getNodeHtml(node); // String
  3933. },
  3934. getNodeChildrenHtml: function(/* DomNode */ dom){
  3935. // summary:
  3936. // Deprecated. Use dijit/_editor/html::getChildrenHtml() instead.
  3937. // tags:
  3938. // deprecated
  3939. kernel.deprecated('dijit.Editor::getNodeChildrenHtml is deprecated','use dijit/_editor/html::getChildrenHtml instead', 2);
  3940. return htmlapi.getChildrenHtml(dom);
  3941. },
  3942. close: function(/*Boolean?*/ save){
  3943. // summary:
  3944. // Kills the editor and optionally writes back the modified contents to the
  3945. // element from which it originated.
  3946. // save:
  3947. // Whether or not to save the changes. If false, the changes are discarded.
  3948. // tags:
  3949. // private
  3950. if(this.isClosed){ return; }
  3951. if(!arguments.length){ save = true; }
  3952. if(save){
  3953. this._set("value", this.getValue(true));
  3954. }
  3955. // line height is squashed for iframes
  3956. // FIXME: why was this here? if(this.iframe){ this.domNode.style.lineHeight = null; }
  3957. if(this.interval){ clearInterval(this.interval); }
  3958. if(this._webkitListener){
  3959. //Cleaup of WebKit fix: #9532
  3960. this.disconnect(this._webkitListener);
  3961. delete this._webkitListener;
  3962. }
  3963. // Guard against memory leaks on IE (see #9268)
  3964. if(has("ie")){
  3965. this.iframe.onfocus = null;
  3966. }
  3967. this.iframe._loadFunc = null;
  3968. if(this._iframeRegHandle){
  3969. this._iframeRegHandle.remove();
  3970. delete this._iframeRegHandle;
  3971. }
  3972. if(this.textarea){
  3973. var s = this.textarea.style;
  3974. s.position = "";
  3975. s.left = s.top = "";
  3976. if(has("ie")){
  3977. s.overflow = this.__overflow;
  3978. this.__overflow = null;
  3979. }
  3980. this.textarea.value = this.value;
  3981. domConstruct.destroy(this.domNode);
  3982. this.domNode = this.textarea;
  3983. }else{
  3984. // Note that this destroys the iframe
  3985. this.domNode.innerHTML = this.value;
  3986. }
  3987. delete this.iframe;
  3988. domClass.remove(this.domNode, this.baseClass);
  3989. this.isClosed = true;
  3990. this.isLoaded = false;
  3991. delete this.editNode;
  3992. delete this.focusNode;
  3993. if(this.window && this.window._frameElement){
  3994. this.window._frameElement = null;
  3995. }
  3996. this.window = null;
  3997. this.document = null;
  3998. this.editingArea = null;
  3999. this.editorObject = null;
  4000. },
  4001. destroy: function(){
  4002. if(!this.isClosed){ this.close(false); }
  4003. if(this._updateTimer){
  4004. clearTimeout(this._updateTimer);
  4005. }
  4006. this.inherited(arguments);
  4007. if(BuxRichText._globalSaveHandler){
  4008. delete BuxRichText._globalSaveHandler[this.id];
  4009. }
  4010. },
  4011. _removeMozBogus: function(/* String */ html){
  4012. // summary:
  4013. // Post filter to remove unwanted HTML attributes generated by mozilla
  4014. // tags:
  4015. // private
  4016. return html.replace(/\stype="_moz"/gi, '').replace(/\s_moz_dirty=""/gi, '').replace(/_moz_resizing="(true|false)"/gi,''); // String
  4017. },
  4018. _removeWebkitBogus: function(/* String */ html){
  4019. // summary:
  4020. // Post filter to remove unwanted HTML attributes generated by webkit
  4021. // tags:
  4022. // private
  4023. html = html.replace(/\sclass="webkit-block-placeholder"/gi, '');
  4024. html = html.replace(/\sclass="apple-style-span"/gi, '');
  4025. // For some reason copy/paste sometime adds extra meta tags for charset on
  4026. // webkit (chrome) on mac.They need to be removed. See: #12007"
  4027. html = html.replace(/<meta charset=\"utf-8\" \/>/gi, '');
  4028. return html; // String
  4029. },
  4030. _normalizeFontStyle: function(/* String */ html){
  4031. // summary:
  4032. // Convert 'strong' and 'em' to 'b' and 'i'.
  4033. // description:
  4034. // Moz can not handle strong/em tags correctly, so to help
  4035. // mozilla and also to normalize output, convert them to 'b' and 'i'.
  4036. //
  4037. // Note the IE generates 'strong' and 'em' rather than 'b' and 'i'
  4038. // tags:
  4039. // private
  4040. return html.replace(/<(\/)?strong([ \>])/gi, '<$1b$2')
  4041. .replace(/<(\/)?em([ \>])/gi, '<$1i$2' ); // String
  4042. },
  4043. _preFixUrlAttributes: function(/* String */ html){
  4044. // summary:
  4045. // Pre-filter to do fixing to href attributes on <a> and <img> tags
  4046. // tags:
  4047. // private
  4048. return html.replace(/(?:(<a(?=\s).*?\shref=)("|')(.*?)\2)|(?:(<a\s.*?href=)([^"'][^ >]+))/gi,
  4049. '$1$4$2$3$5$2 _djrealurl=$2$3$5$2')
  4050. .replace(/(?:(<img(?=\s).*?\ssrc=)("|')(.*?)\2)|(?:(<img\s.*?src=)([^"'][^ >]+))/gi,
  4051. '$1$4$2$3$5$2 _djrealurl=$2$3$5$2'); // String
  4052. },
  4053. /*****************************************************************************
  4054. The following functions implement HTML manipulation commands for various
  4055. browser/contentEditable implementations. The goal of them is to enforce
  4056. standard behaviors of them.
  4057. ******************************************************************************/
  4058. /*** queryCommandEnabled implementations ***/
  4059. _browserQueryCommandEnabled: function(command){
  4060. // summary:
  4061. // Implementation to call to the native queryCommandEnabled of the browser.
  4062. // command:
  4063. // The command to check.
  4064. // tags:
  4065. // protected
  4066. if(!command) { return false; }
  4067. var elem = has("ie") < 9 ? this.document.selection.createRange() : this.document;
  4068. try{
  4069. return elem.queryCommandEnabled(command);
  4070. }catch(e){
  4071. return false;
  4072. }
  4073. },
  4074. _createlinkEnabledImpl: function(/*===== argument =====*/){
  4075. // summary:
  4076. // This function implements the test for if the create link
  4077. // command should be enabled or not.
  4078. // argument:
  4079. // arguments to the exec command, if any.
  4080. // tags:
  4081. // protected
  4082. var enabled = true;
  4083. if(has("opera")){
  4084. var sel = this.window.getSelection();
  4085. if(sel.isCollapsed){
  4086. enabled = true;
  4087. }else{
  4088. enabled = this.document.queryCommandEnabled("createlink");
  4089. }
  4090. }else{
  4091. enabled = this._browserQueryCommandEnabled("createlink");
  4092. }
  4093. return enabled;
  4094. },
  4095. _unlinkEnabledImpl: function(/*===== argument =====*/){
  4096. // summary:
  4097. // This function implements the test for if the unlink
  4098. // command should be enabled or not.
  4099. // argument:
  4100. // arguments to the exec command, if any.
  4101. // tags:
  4102. // protected
  4103. var enabled = true;
  4104. if(has("mozilla") || has("webkit")){
  4105. enabled = this._sCall("hasAncestorElement", ["a"]);
  4106. }else{
  4107. enabled = this._browserQueryCommandEnabled("unlink");
  4108. }
  4109. return enabled;
  4110. },
  4111. _inserttableEnabledImpl: function(/*===== argument =====*/){
  4112. // summary:
  4113. // This function implements the test for if the inserttable
  4114. // command should be enabled or not.
  4115. // argument:
  4116. // arguments to the exec command, if any.
  4117. // tags:
  4118. // protected
  4119. var enabled = true;
  4120. if(has("mozilla") || has("webkit")){
  4121. enabled = true;
  4122. }else{
  4123. enabled = this._browserQueryCommandEnabled("inserttable");
  4124. }
  4125. return enabled;
  4126. },
  4127. _cutEnabledImpl: function(/*===== argument =====*/){
  4128. // summary:
  4129. // This function implements the test for if the cut
  4130. // command should be enabled or not.
  4131. // argument:
  4132. // arguments to the exec command, if any.
  4133. // tags:
  4134. // protected
  4135. var enabled = true;
  4136. if(has("webkit")){
  4137. // WebKit deems clipboard activity as a security threat and natively would return false
  4138. var sel = this.window.getSelection();
  4139. if(sel){ sel = sel.toString(); }
  4140. enabled = !!sel;
  4141. }else{
  4142. enabled = this._browserQueryCommandEnabled("cut");
  4143. }
  4144. return enabled;
  4145. },
  4146. _copyEnabledImpl: function(/*===== argument =====*/){
  4147. // summary:
  4148. // This function implements the test for if the copy
  4149. // command should be enabled or not.
  4150. // argument:
  4151. // arguments to the exec command, if any.
  4152. // tags:
  4153. // protected
  4154. var enabled = true;
  4155. if(has("webkit")){
  4156. // WebKit deems clipboard activity as a security threat and natively would return false
  4157. var sel = this.window.getSelection();
  4158. if(sel){ sel = sel.toString(); }
  4159. enabled = !!sel;
  4160. }else{
  4161. enabled = this._browserQueryCommandEnabled("copy");
  4162. }
  4163. return enabled;
  4164. },
  4165. _pasteEnabledImpl: function(/*===== argument =====*/){
  4166. // summary:c
  4167. // This function implements the test for if the paste
  4168. // command should be enabled or not.
  4169. // argument:
  4170. // arguments to the exec command, if any.
  4171. // tags:
  4172. // protected
  4173. var enabled = true;
  4174. if(has("webkit")){
  4175. return true;
  4176. }else{
  4177. enabled = this._browserQueryCommandEnabled("paste");
  4178. }
  4179. return enabled;
  4180. },
  4181. /*** execCommand implementations ***/
  4182. _inserthorizontalruleImpl: function(argument){
  4183. // summary:
  4184. // This function implements the insertion of HTML 'HR' tags.
  4185. // into a point on the page. IE doesn't to it right, so
  4186. // we have to use an alternate form
  4187. // argument:
  4188. // arguments to the exec command, if any.
  4189. // tags:
  4190. // protected
  4191. if(has("ie")){
  4192. return this._inserthtmlImpl("<hr>");
  4193. }
  4194. return this.document.execCommand("inserthorizontalrule", false, argument);
  4195. },
  4196. _unlinkImpl: function(argument){
  4197. // summary:
  4198. // This function implements the unlink of an 'a' tag.
  4199. // argument:
  4200. // arguments to the exec command, if any.
  4201. // tags:
  4202. // protected
  4203. if((this.queryCommandEnabled("unlink")) && (has("mozilla") || has("webkit"))){
  4204. var a = this._sCall("getAncestorElement", [ "a" ]);
  4205. this._sCall("selectElement", [ a ]);
  4206. return this.document.execCommand("unlink", false, null);
  4207. }
  4208. return this.document.execCommand("unlink", false, argument);
  4209. },
  4210. _hilitecolorImpl: function(argument){
  4211. // summary:
  4212. // This function implements the hilitecolor command
  4213. // argument:
  4214. // arguments to the exec command, if any.
  4215. // tags:
  4216. // protected
  4217. var returnValue;
  4218. var isApplied = this._handleTextColorOrProperties("hilitecolor", argument);
  4219. if(!isApplied){
  4220. if(has("mozilla")){
  4221. // mozilla doesn't support hilitecolor properly when useCSS is
  4222. // set to false (bugzilla #279330)
  4223. this.document.execCommand("styleWithCSS", false, true);
  4224. console.log("Executing color command.");
  4225. returnValue = this.document.execCommand("hilitecolor", false, argument);
  4226. this.document.execCommand("styleWithCSS", false, false);
  4227. }else{
  4228. returnValue = this.document.execCommand("hilitecolor", false, argument);
  4229. }
  4230. }
  4231. return returnValue;
  4232. },
  4233. _backcolorImpl: function(argument){
  4234. // summary:
  4235. // This function implements the backcolor command
  4236. // argument:
  4237. // arguments to the exec command, if any.
  4238. // tags:
  4239. // protected
  4240. if(has("ie")){
  4241. // Tested under IE 6 XP2, no problem here, comment out
  4242. // IE weirdly collapses ranges when we exec these commands, so prevent it
  4243. // var tr = this.document.selection.createRange();
  4244. argument = argument ? argument : null;
  4245. }
  4246. var isApplied = this._handleTextColorOrProperties("backcolor", argument);
  4247. if(!isApplied){
  4248. isApplied = this.document.execCommand("backcolor", false, argument);
  4249. }
  4250. return isApplied;
  4251. },
  4252. _forecolorImpl: function(argument){
  4253. // summary:
  4254. // This function implements the forecolor command
  4255. // argument:
  4256. // arguments to the exec command, if any.
  4257. // tags:
  4258. // protected
  4259. if(has("ie")){
  4260. // Tested under IE 6 XP2, no problem here, comment out
  4261. // IE weirdly collapses ranges when we exec these commands, so prevent it
  4262. // var tr = this.document.selection.createRange();
  4263. argument = argument? argument : null;
  4264. }
  4265. var isApplied = false;
  4266. isApplied = this._handleTextColorOrProperties("forecolor", argument);
  4267. if(!isApplied){
  4268. isApplied = this.document.execCommand("forecolor", false, argument);
  4269. }
  4270. return isApplied;
  4271. },
  4272. _inserthtmlImpl: function(argument){
  4273. // summary:
  4274. // This function implements the insertion of HTML content into
  4275. // a point on the page.
  4276. // argument:
  4277. // The content to insert, if any.
  4278. // tags:
  4279. // protected
  4280. argument = this._preFilterContent(argument);
  4281. var rv = true;
  4282. if(has("ie") < 9){
  4283. var insertRange = this.document.selection.createRange();
  4284. if(this.document.selection.type.toUpperCase() === 'CONTROL'){
  4285. var n = insertRange.item(0);
  4286. while(insertRange.length){
  4287. insertRange.remove(insertRange.item(0));
  4288. }
  4289. n.outerHTML = argument;
  4290. }else{
  4291. insertRange.pasteHTML(argument);
  4292. }
  4293. insertRange.select();
  4294. }else if(has("trident") < 8){
  4295. var insertRange;
  4296. var selection = rangeapi.getSelection(this.window);
  4297. if(selection && selection.rangeCount && selection.getRangeAt){
  4298. insertRange = selection.getRangeAt(0);
  4299. insertRange.deleteContents();
  4300. var div = domConstruct.create('div');
  4301. div.innerHTML = argument;
  4302. var node, lastNode;
  4303. var n = this.document.createDocumentFragment();
  4304. while((node = div.firstChild)){
  4305. lastNode = n.appendChild(node);
  4306. }
  4307. insertRange.insertNode(n);
  4308. if(lastNode) {
  4309. insertRange = insertRange.cloneRange();
  4310. insertRange.setStartAfter(lastNode);
  4311. insertRange.collapse(false);
  4312. selection.removeAllRanges();
  4313. selection.addRange(insertRange);
  4314. }
  4315. }
  4316. }else if(has("mozilla") && !argument.length){
  4317. //mozilla can not inserthtml an empty html to delete current selection
  4318. //so we delete the selection instead in this case
  4319. this._sCall("remove"); // FIXME
  4320. }else{
  4321. rv = this.document.execCommand("inserthtml", false, argument);
  4322. }
  4323. return rv;
  4324. },
  4325. _boldImpl: function(argument){
  4326. // summary:
  4327. // This function implements an over-ride of the bold command.
  4328. // argument:
  4329. // Not used, operates by selection.
  4330. // tags:
  4331. // protected
  4332. var applied = false;
  4333. if(has("ie") || has("trident")){
  4334. this._adaptIESelection();
  4335. applied = this._adaptIEFormatAreaAndExec("bold");
  4336. }
  4337. if(!applied){
  4338. applied = this.document.execCommand("bold", false, argument);
  4339. }
  4340. return applied;
  4341. },
  4342. _italicImpl: function(argument){
  4343. // summary:
  4344. // This function implements an over-ride of the italic command.
  4345. // argument:
  4346. // Not used, operates by selection.
  4347. // tags:
  4348. // protected
  4349. var applied = false;
  4350. if(has("ie") || has("trident")){
  4351. this._adaptIESelection();
  4352. applied = this._adaptIEFormatAreaAndExec("italic");
  4353. }
  4354. if(!applied){
  4355. applied = this.document.execCommand("italic", false, argument);
  4356. }
  4357. return applied;
  4358. },
  4359. _underlineImpl: function(argument){
  4360. // summary:
  4361. // This function implements an over-ride of the underline command.
  4362. // argument:
  4363. // Not used, operates by selection.
  4364. // tags:
  4365. // protected
  4366. var applied = false;
  4367. if(has("ie") || has("trident")){
  4368. this._adaptIESelection();
  4369. applied = this._adaptIEFormatAreaAndExec("underline");
  4370. }
  4371. if(!applied){
  4372. applied = this.document.execCommand("underline", false, argument);
  4373. }
  4374. return applied;
  4375. },
  4376. _strikethroughImpl: function(argument){
  4377. // summary:
  4378. // This function implements an over-ride of the strikethrough command.
  4379. // argument:
  4380. // Not used, operates by selection.
  4381. // tags:
  4382. // protected
  4383. var applied = false;
  4384. if(has("ie") || has("trident")){
  4385. this._adaptIESelection();
  4386. applied = this._adaptIEFormatAreaAndExec("strikethrough");
  4387. }
  4388. if(!applied){
  4389. applied = this.document.execCommand("strikethrough", false, argument);
  4390. }
  4391. return applied;
  4392. },
  4393. _superscriptImpl: function(argument){
  4394. // summary:
  4395. // This function implements an over-ride of the superscript command.
  4396. // argument:
  4397. // Not used, operates by selection.
  4398. // tags:
  4399. // protected
  4400. var applied = false;
  4401. if(has("ie") || has("trident")){
  4402. this._adaptIESelection();
  4403. applied = this._adaptIEFormatAreaAndExec("superscript");
  4404. }
  4405. if(!applied){
  4406. applied = this.document.execCommand("superscript", false, argument);
  4407. }
  4408. return applied;
  4409. },
  4410. _subscriptImpl: function(argument){
  4411. // summary:
  4412. // This function implements an over-ride of the superscript command.
  4413. // argument:
  4414. // Not used, operates by selection.
  4415. // tags:
  4416. // protected
  4417. var applied = false;
  4418. if(has("ie") || has("trident")){
  4419. this._adaptIESelection();
  4420. applied = this._adaptIEFormatAreaAndExec("subscript");
  4421. }
  4422. if(!applied){
  4423. applied = this.document.execCommand("subscript", false, argument);
  4424. }
  4425. return applied;
  4426. },
  4427. _fontnameImpl: function(argument){
  4428. // summary:
  4429. // This function implements the fontname command
  4430. // argument:
  4431. // arguments to the exec command, if any.
  4432. // tags:
  4433. // protected
  4434. var isApplied;
  4435. if(has("ie") || has("trident")){
  4436. isApplied = this._handleTextColorOrProperties("fontname", argument);
  4437. }
  4438. if(!isApplied){
  4439. isApplied = this.document.execCommand("fontname", false, argument);
  4440. }
  4441. return isApplied;
  4442. },
  4443. _fontsizeImpl: function(argument){
  4444. // summary:
  4445. // This function implements the fontsize command
  4446. // argument:
  4447. // arguments to the exec command, if any.
  4448. // tags:
  4449. // protected
  4450. var isApplied;
  4451. if(has("ie") || has("trident")){
  4452. isApplied = this._handleTextColorOrProperties("fontsize", argument);
  4453. }
  4454. if(!isApplied){
  4455. isApplied = this.document.execCommand("fontsize", false, argument);
  4456. }
  4457. return isApplied;
  4458. },
  4459. _insertorderedlistImpl: function(argument){
  4460. // summary:
  4461. // This function implements the insertorderedlist command
  4462. // argument:
  4463. // arguments to the exec command, if any.
  4464. // tags:
  4465. // protected
  4466. var applied = false;
  4467. if(has("ie") || has("trident")){
  4468. applied = this._adaptIEList("insertorderedlist", argument);
  4469. }
  4470. if(!applied){
  4471. applied = this.document.execCommand("insertorderedlist", false, argument);
  4472. }
  4473. return applied;
  4474. },
  4475. _insertunorderedlistImpl: function(argument){
  4476. // summary:
  4477. // This function implements the insertunorderedlist command
  4478. // argument:
  4479. // arguments to the exec command, if any.
  4480. // tags:
  4481. // protected
  4482. var applied = false;
  4483. if(has("ie") || has("trident")){
  4484. applied = this._adaptIEList("insertunorderedlist", argument);
  4485. }
  4486. if(!applied){
  4487. applied = this.document.execCommand("insertunorderedlist", false, argument);
  4488. }
  4489. return applied;
  4490. },
  4491. getHeaderHeight: function(){
  4492. // summary:
  4493. // A function for obtaining the height of the header node
  4494. return this._getNodeChildrenHeight(this.header); // Number
  4495. },
  4496. getFooterHeight: function(){
  4497. // summary:
  4498. // A function for obtaining the height of the footer node
  4499. return this._getNodeChildrenHeight(this.footer); // Number
  4500. },
  4501. _getNodeChildrenHeight: function(node){
  4502. // summary:
  4503. // An internal function for computing the cumulative height of all child nodes of 'node'
  4504. // node:
  4505. // The node to process the children of;
  4506. var h = 0;
  4507. if(node && node.childNodes){
  4508. // IE didn't compute it right when position was obtained on the node directly is some cases,
  4509. // so we have to walk over all the children manually.
  4510. var i;
  4511. for(i = 0; i < node.childNodes.length; i++){
  4512. var size = domGeometry.position(node.childNodes[i]);
  4513. h += size.h;
  4514. }
  4515. }
  4516. return h; // Number
  4517. },
  4518. _isNodeEmpty: function(node, startOffset){
  4519. // summary:
  4520. // Function to test if a node is devoid of real content.
  4521. // node:
  4522. // The node to check.
  4523. // tags:
  4524. // private.
  4525. if(node.nodeType === 1/*element*/){
  4526. if(node.childNodes.length > 0){
  4527. return this._isNodeEmpty(node.childNodes[0], startOffset);
  4528. }
  4529. return true;
  4530. }else if(node.nodeType === 3/*text*/){
  4531. return (node.nodeValue.substring(startOffset) === "");
  4532. }
  4533. return false;
  4534. },
  4535. _removeStartingRangeFromRange: function(node, range){
  4536. // summary:
  4537. // Function to adjust selection range by removing the current
  4538. // start node.
  4539. // node:
  4540. // The node to remove from the starting range.
  4541. // range:
  4542. // The range to adapt.
  4543. // tags:
  4544. // private
  4545. if(node.nextSibling){
  4546. range.setStart(node.nextSibling,0);
  4547. }else{
  4548. var parent = node.parentNode;
  4549. while(parent && parent.nextSibling == null){
  4550. //move up the tree until we find a parent that has another node, that node will be the next node
  4551. parent = parent.parentNode;
  4552. }
  4553. if(parent){
  4554. range.setStart(parent.nextSibling,0);
  4555. }
  4556. }
  4557. return range;
  4558. },
  4559. _adaptIESelection: function(){
  4560. // summary:
  4561. // Function to adapt the IE range by removing leading 'newlines'
  4562. // Needed to fix issue with bold/italics/underline not working if
  4563. // range included leading 'newlines'.
  4564. // In IE, if a user starts a selection at the very end of a line,
  4565. // then the native browser commands will fail to execute correctly.
  4566. // To work around the issue, we can remove all empty nodes from
  4567. // the start of the range selection.
  4568. var selection = rangeapi.getSelection(this.window);
  4569. if(selection && selection.rangeCount && !selection.isCollapsed){
  4570. var range = selection.getRangeAt(0);
  4571. var firstNode = range.startContainer;
  4572. var startOffset = range.startOffset;
  4573. while(firstNode.nodeType === 3/*text*/ && startOffset >= firstNode.length && firstNode.nextSibling){
  4574. //traverse the text nodes until we get to the one that is actually highlighted
  4575. startOffset = startOffset - firstNode.length;
  4576. firstNode = firstNode.nextSibling;
  4577. }
  4578. //Remove the starting ranges until the range does not start with an empty node.
  4579. var lastNode=null;
  4580. while(this._isNodeEmpty(firstNode, startOffset) && firstNode !== lastNode){
  4581. lastNode =firstNode; //this will break the loop in case we can't find the next sibling
  4582. range = this._removeStartingRangeFromRange(firstNode, range); //move the start container to the next node in the range
  4583. firstNode = range.startContainer;
  4584. startOffset = 0; //start at the beginning of the new starting range
  4585. }
  4586. selection.removeAllRanges();// this will work as long as users cannot select multiple ranges. I have not been able to do that in the editor.
  4587. selection.addRange(range);
  4588. }
  4589. },
  4590. _adaptIEFormatAreaAndExec: function(command){
  4591. // summary:
  4592. // Function to handle IE's quirkiness regarding how it handles
  4593. // format commands on a word. This involves a lit of node splitting
  4594. // and format cloning.
  4595. // command:
  4596. // The format command, needed to check if the desired
  4597. // command is true or not.
  4598. var selection = rangeapi.getSelection(this.window);
  4599. var doc = this.document;
  4600. var rs, ret, range, txt, startNode, endNode, breaker, sNode;
  4601. if(command && selection && selection.isCollapsed){
  4602. var isApplied = this.queryCommandValue(command);
  4603. if(isApplied){
  4604. // We have to split backwards until we hit the format
  4605. var nNames = this._tagNamesForCommand(command);
  4606. range = selection.getRangeAt(0);
  4607. var fs = range.startContainer;
  4608. if(fs.nodeType === 3){
  4609. var offset = range.endOffset;
  4610. if(fs.length < offset){
  4611. //We are not looking from the right node, try to locate the correct one
  4612. ret = this._adjustNodeAndOffset(rs, offset);
  4613. fs = ret.node;
  4614. offset = ret.offset;
  4615. }
  4616. }
  4617. var topNode;
  4618. while(fs && fs !== this.editNode){
  4619. // We have to walk back and see if this is still a format or not.
  4620. // Hm, how do I do this?
  4621. var tName = fs.tagName? fs.tagName.toLowerCase() : "";
  4622. if(array.indexOf(nNames, tName) > -1){
  4623. topNode = fs;
  4624. break;
  4625. }
  4626. fs = fs.parentNode;
  4627. }
  4628. // Okay, we have a stopping place, time to split things apart.
  4629. if(topNode){
  4630. // Okay, we know how far we have to split backwards, so we have to split now.
  4631. rs = range.startContainer;
  4632. var newblock = doc.createElement(topNode.tagName);
  4633. domConstruct.place(newblock, topNode, "after");
  4634. if(rs && rs.nodeType === 3){
  4635. // Text node, we have to split it.
  4636. var nodeToMove, tNode;
  4637. var endOffset = range.endOffset;
  4638. if(rs.length < endOffset){
  4639. //We are not splitting the right node, try to locate the correct one
  4640. ret = this._adjustNodeAndOffset(rs, endOffset);
  4641. rs = ret.node;
  4642. endOffset = ret.offset;
  4643. }
  4644. txt = rs.nodeValue;
  4645. startNode = doc.createTextNode(txt.substring(0, endOffset));
  4646. var endText = txt.substring(endOffset, txt.length);
  4647. if(endText){
  4648. endNode = doc.createTextNode(endText);
  4649. }
  4650. // Place the split, then remove original nodes.
  4651. domConstruct.place(startNode, rs, "before");
  4652. if(endNode){
  4653. breaker = doc.createElement("span");
  4654. breaker.className = "ieFormatBreakerSpan";
  4655. domConstruct.place(breaker, rs, "after");
  4656. domConstruct.place(endNode, breaker, "after");
  4657. endNode = breaker;
  4658. }
  4659. domConstruct.destroy(rs);
  4660. // Okay, we split the text. Now we need to see if we're
  4661. // parented to the block element we're splitting and if
  4662. // not, we have to split all the way up. Ugh.
  4663. var parentC = startNode.parentNode;
  4664. var tagList = [];
  4665. var tagData;
  4666. while(parentC !== topNode){
  4667. var tg = parentC.tagName;
  4668. tagData = {tagName: tg};
  4669. tagList.push(tagData);
  4670. var newTg = doc.createElement(tg);
  4671. // Clone over any 'style' data.
  4672. if(parentC.style){
  4673. if(newTg.style){
  4674. if(parentC.style.cssText){
  4675. newTg.style.cssText = parentC.style.cssText;
  4676. tagData.cssText = parentC.style.cssText;
  4677. }
  4678. }
  4679. }
  4680. // If font also need to clone over any font data.
  4681. if(parentC.tagName === "FONT"){
  4682. if(parentC.color){
  4683. newTg.color = parentC.color;
  4684. tagData.color = parentC.color;
  4685. }
  4686. if(parentC.face){
  4687. newTg.face = parentC.face;
  4688. tagData.face = parentC.face;
  4689. }
  4690. if(parentC.size){ // this check was necessary on IE
  4691. newTg.size = parentC.size;
  4692. tagData.size = parentC.size;
  4693. }
  4694. }
  4695. if(parentC.className){
  4696. newTg.className = parentC.className;
  4697. tagData.className = parentC.className;
  4698. }
  4699. // Now move end node and every sibling
  4700. // after it over into the new tag.
  4701. if(endNode){
  4702. nodeToMove = endNode;
  4703. while(nodeToMove){
  4704. tNode = nodeToMove.nextSibling;
  4705. newTg.appendChild(nodeToMove);
  4706. nodeToMove = tNode;
  4707. }
  4708. }
  4709. if(newTg.tagName == parentC.tagName){
  4710. breaker = doc.createElement("span");
  4711. breaker.className = "ieFormatBreakerSpan";
  4712. domConstruct.place(breaker, parentC, "after");
  4713. domConstruct.place(newTg, breaker, "after");
  4714. }else{
  4715. domConstruct.place(newTg, parentC, "after");
  4716. }
  4717. startNode = parentC;
  4718. endNode = newTg;
  4719. parentC = parentC.parentNode;
  4720. }
  4721. // Lastly, move the split out all the split tags
  4722. // to the new block as they should now be split properly.
  4723. if(endNode){
  4724. nodeToMove = endNode;
  4725. if(nodeToMove.nodeType === 1 || (nodeToMove.nodeType === 3 && nodeToMove.nodeValue)){
  4726. // Non-blank text and non-text nodes need to clear out that blank space
  4727. // before moving the contents.
  4728. newblock.innerHTML = "";
  4729. }
  4730. while(nodeToMove){
  4731. tNode = nodeToMove.nextSibling;
  4732. newblock.appendChild(nodeToMove);
  4733. nodeToMove = tNode;
  4734. }
  4735. }
  4736. // We had intermediate tags, we have to now recreate them inbetween the split
  4737. // and restore what styles, classnames, etc, we can.
  4738. if(tagList.length){
  4739. tagData = tagList.pop();
  4740. var newContTag = doc.createElement(tagData.tagName);
  4741. if(tagData.cssText && newContTag.style){
  4742. newContTag.style.cssText = tagData.cssText;
  4743. }
  4744. if(tagData.className){
  4745. newContTag.className = tagData.className;
  4746. }
  4747. if(tagData.tagName === "FONT"){
  4748. if(tagData.color){
  4749. newContTag.color = tagData.color;
  4750. }
  4751. if(tagData.face){
  4752. newContTag.face = tagData.face;
  4753. }
  4754. if(tagData.size){
  4755. newContTag.size = tagData.size;
  4756. }
  4757. }
  4758. domConstruct.place(newContTag, newblock, "before");
  4759. while(tagList.length){
  4760. tagData = tagList.pop();
  4761. var newTgNode = doc.createElement(tagData.tagName);
  4762. if(tagData.cssText && newTgNode.style){
  4763. newTgNode.style.cssText = tagData.cssText;
  4764. }
  4765. if(tagData.className){
  4766. newTgNode.className = tagData.className;
  4767. }
  4768. if(tagData.tagName === "FONT"){
  4769. if(tagData.color){
  4770. newTgNode.color = tagData.color;
  4771. }
  4772. if(tagData.face){
  4773. newTgNode.face = tagData.face;
  4774. }
  4775. if(tagData.size){
  4776. newTgNode.size = tagData.size;
  4777. }
  4778. }
  4779. newContTag.appendChild(newTgNode);
  4780. newContTag = newTgNode;
  4781. }
  4782. // Okay, everything is theoretically split apart and removed from the content
  4783. // so insert the dummy text to select, select it, then
  4784. // clear to position cursor.
  4785. sNode = doc.createTextNode(".");
  4786. breaker.appendChild(sNode);
  4787. newContTag.appendChild(sNode);
  4788. win.withGlobal(this.window, lang.hitch(this, function(){
  4789. var newrange = rangeapi.create();
  4790. newrange.setStart(sNode, 0);
  4791. newrange.setEnd(sNode, sNode.length);
  4792. selection.removeAllRanges();
  4793. selection.addRange(newrange);
  4794. selectionapi.collapse(false);
  4795. sNode.parentNode.innerHTML = "";
  4796. }));
  4797. }else{
  4798. // No extra tags, so we have to insert a breaker point and rely
  4799. // on filters to remove it later.
  4800. breaker = doc.createElement("span");
  4801. breaker.className="ieFormatBreakerSpan";
  4802. sNode = doc.createTextNode(".");
  4803. breaker.appendChild(sNode);
  4804. domConstruct.place(breaker, newblock, "before");
  4805. win.withGlobal(this.window, lang.hitch(this, function(){
  4806. var newrange = rangeapi.create();
  4807. newrange.setStart(sNode, 0);
  4808. newrange.setEnd(sNode, sNode.length);
  4809. selection.removeAllRanges();
  4810. selection.addRange(newrange);
  4811. selectionapi.collapse(false);
  4812. sNode.parentNode.innerHTML = "";
  4813. }));
  4814. }
  4815. if(!newblock.firstChild){
  4816. // Empty, we don't need it. Split was at end or similar
  4817. // So, remove it.
  4818. domConstruct.destroy(newblock);
  4819. }
  4820. return true;
  4821. }
  4822. }
  4823. return false;
  4824. }else{
  4825. range = selection.getRangeAt(0);
  4826. rs = range.startContainer;
  4827. if(rs && rs.nodeType === 3){
  4828. // Text node, we have to split it.
  4829. win.withGlobal(this.window, lang.hitch(this, function(){
  4830. var offset = range.startOffset;
  4831. if(rs.length < offset){
  4832. //We are not splitting the right node, try to locate the correct one
  4833. ret = this._adjustNodeAndOffset(rs, offset);
  4834. rs = ret.node;
  4835. offset = ret.offset;
  4836. }
  4837. txt = rs.nodeValue;
  4838. startNode = doc.createTextNode(txt.substring(0, offset));
  4839. var endText = txt.substring(offset);
  4840. if(endText !== ""){
  4841. endNode = doc.createTextNode(txt.substring(offset));
  4842. }
  4843. // Create a space, we'll select and bold it, so
  4844. // the whole word doesn't get bolded
  4845. breaker = doc.createElement("span");
  4846. sNode = doc.createTextNode(".");
  4847. breaker.appendChild(sNode);
  4848. if(startNode.length){
  4849. domConstruct.place(startNode, rs, "after");
  4850. }else{
  4851. startNode = rs;
  4852. }
  4853. domConstruct.place(breaker, startNode, "after");
  4854. if(endNode){
  4855. domConstruct.place(endNode, breaker, "after");
  4856. }
  4857. domConstruct.destroy(rs);
  4858. var newrange = rangeapi.create();
  4859. newrange.setStart(sNode, 0);
  4860. newrange.setEnd(sNode, sNode.length);
  4861. selection.removeAllRanges();
  4862. selection.addRange(newrange);
  4863. doc.execCommand(command);
  4864. domConstruct.place(breaker.firstChild, breaker, "before");
  4865. domConstruct.destroy(breaker);
  4866. newrange.setStart(sNode, 0);
  4867. newrange.setEnd(sNode, sNode.length);
  4868. selection.removeAllRanges();
  4869. selection.addRange(newrange);
  4870. selectionapi.collapse(false);
  4871. sNode.parentNode.innerHTML = "";
  4872. }));
  4873. return true;
  4874. }
  4875. }
  4876. }else{
  4877. return false;
  4878. }
  4879. },
  4880. _adaptIEList: function(command /*===== , argument =====*/){
  4881. // summary:
  4882. // This function handles normalizing the IE list behavior as
  4883. // much as possible.
  4884. // command:
  4885. // The list command to execute.
  4886. // argument:
  4887. // Any additional argument.
  4888. // tags:
  4889. // private
  4890. var selection = rangeapi.getSelection(this.window);
  4891. if(selection.isCollapsed){
  4892. // In the case of no selection, lets commonize the behavior and
  4893. // make sure that it indents if needed.
  4894. if(selection.rangeCount && !this.queryCommandValue(command)){
  4895. var range = selection.getRangeAt(0);
  4896. var sc = range.startContainer;
  4897. if(sc && sc.nodeType == 3){
  4898. // text node. Lets see if there is a node before it that isn't
  4899. // some sort of breaker.
  4900. if(!range.startOffset){
  4901. // We're at the beginning of a text area. It may have been br split
  4902. // Who knows? In any event, we must create the list manually
  4903. // or IE may shove too much into the list element. It seems to
  4904. // grab content before the text node too if it's br split.
  4905. // Why can't IE work like everyone else?
  4906. win.withGlobal(this.window, lang.hitch(this, function(){
  4907. // Create a space, we'll select and bold it, so
  4908. // the whole word doesn't get bolded
  4909. var lType = "ul";
  4910. if(command === "insertorderedlist"){
  4911. lType = "ol";
  4912. }
  4913. var list = domConstruct.create(lType);
  4914. var li = domConstruct.create("li", null, list);
  4915. domConstruct.place(list, sc, "before");
  4916. // Move in the text node as part of the li.
  4917. li.appendChild(sc);
  4918. // We need a br after it or the enter key handler
  4919. // sometimes throws errors.
  4920. domConstruct.create("br", null, list, "after");
  4921. // Okay, now lets move our cursor to the beginning.
  4922. var newrange = rangeapi.create();
  4923. newrange.setStart(sc, 0);
  4924. newrange.setEnd(sc, sc.length);
  4925. selection.removeAllRanges();
  4926. selection.addRange(newrange);
  4927. selectionapi.collapse(true);
  4928. }));
  4929. return true;
  4930. }
  4931. }
  4932. }
  4933. }
  4934. return false;
  4935. },
  4936. _handleTextColorOrProperties: function(command, argument){
  4937. // summary:
  4938. // This function handles appplying text color as best it is
  4939. // able to do so when the selection is collapsed, making the
  4940. // behavior cross-browser consistent. It also handles the name
  4941. // and size for IE.
  4942. // command:
  4943. // The command.
  4944. // argument:
  4945. // Any additional arguments.
  4946. // tags:
  4947. // private
  4948. var selection = rangeapi.getSelection(this.window);
  4949. var doc = this.document;
  4950. var rs, ret, range, txt, startNode, endNode, breaker, sNode;
  4951. argument = argument || null;
  4952. if(command && selection && selection.isCollapsed){
  4953. if(selection.rangeCount){
  4954. range = selection.getRangeAt(0);
  4955. rs = range.startContainer;
  4956. if(rs && rs.nodeType === 3){
  4957. // Text node, we have to split it.
  4958. win.withGlobal(this.window, lang.hitch(this, function(){
  4959. var offset = range.startOffset;
  4960. if(rs.length < offset){
  4961. //We are not splitting the right node, try to locate the correct one
  4962. ret = this._adjustNodeAndOffset(rs, offset);
  4963. rs = ret.node;
  4964. offset = ret.offset;
  4965. }
  4966. txt = rs.nodeValue;
  4967. startNode = doc.createTextNode(txt.substring(0, offset));
  4968. var endText = txt.substring(offset);
  4969. if(endText !== ""){
  4970. endNode = doc.createTextNode(txt.substring(offset));
  4971. }
  4972. // Create a space, we'll select and bold it, so
  4973. // the whole word doesn't get bolded
  4974. breaker = domConstruct.create("span");
  4975. sNode = doc.createTextNode(".");
  4976. breaker.appendChild(sNode);
  4977. // Create a junk node to avoid it trying to stlye the breaker.
  4978. // This will get destroyed later.
  4979. var extraSpan = domConstruct.create("span");
  4980. breaker.appendChild(extraSpan);
  4981. if(startNode.length){
  4982. domConstruct.place(startNode, rs, "after");
  4983. }else{
  4984. startNode = rs;
  4985. }
  4986. domConstruct.place(breaker, startNode, "after");
  4987. if(endNode){
  4988. domConstruct.place(endNode, breaker, "after");
  4989. }
  4990. domConstruct.destroy(rs);
  4991. var newrange = rangeapi.create();
  4992. newrange.setStart(sNode, 0);
  4993. newrange.setEnd(sNode, sNode.length);
  4994. selection.removeAllRanges();
  4995. selection.addRange(newrange);
  4996. if(has("webkit")){
  4997. // WebKit is frustrating with positioning the cursor.
  4998. // It stinks to have a selected space, but there really
  4999. // isn't much choice here.
  5000. var style = "color";
  5001. if(command === "hilitecolor" || command === "backcolor"){
  5002. style = "backgroundColor";
  5003. }
  5004. domStyle.set(breaker, style, argument);
  5005. selectionapi.remove();
  5006. domConstruct.destroy(extraSpan);
  5007. breaker.innerHTML = "&#160;"; // &nbsp;
  5008. selectionapi.selectElement(breaker);
  5009. this.focus();
  5010. }else{
  5011. this.execCommand(command, argument);
  5012. domConstruct.place(breaker.firstChild, breaker, "before");
  5013. domConstruct.destroy(breaker);
  5014. newrange.setStart(sNode, 0);
  5015. newrange.setEnd(sNode, sNode.length);
  5016. selection.removeAllRanges();
  5017. selection.addRange(newrange);
  5018. selectionapi.collapse(false);
  5019. sNode.parentNode.removeChild(sNode);
  5020. }
  5021. }));
  5022. return true;
  5023. }
  5024. }
  5025. }
  5026. return false;
  5027. },
  5028. _adjustNodeAndOffset: function(/*DomNode*/node, /*Int*/offset){
  5029. // summary:
  5030. // In the case there are multiple text nodes in a row the offset may not be within the node.
  5031. // If the offset is larger than the node length, it will attempt to find
  5032. // the next text sibling until it locates the text node in which the offset refers to
  5033. // node:
  5034. // The node to check.
  5035. // offset:
  5036. // The position to find within the text node
  5037. // tags:
  5038. // private.
  5039. while(node.length < offset && node.nextSibling && node.nextSibling.nodeType === 3){
  5040. //Adjust the offset and node in the case of multiple text nodes in a row
  5041. offset = offset - node.length;
  5042. node = node.nextSibling;
  5043. }
  5044. return {"node": node, "offset": offset};
  5045. },
  5046. _tagNamesForCommand: function(command){
  5047. // summary:
  5048. // Function to return the tab names that are associated
  5049. // with a particular style.
  5050. // command: String
  5051. // The command to return tags for.
  5052. // tags:
  5053. // private
  5054. if(command === "bold"){
  5055. return ["b", "strong"];
  5056. }else if(command === "italic"){
  5057. return ["i","em"];
  5058. }else if(command === "strikethrough"){
  5059. return ["s", "strike"];
  5060. }else if(command === "superscript"){
  5061. return ["sup"];
  5062. }else if(command === "subscript"){
  5063. return ["sub"];
  5064. }else if(command === "underline"){
  5065. return ["u"];
  5066. }
  5067. return [];
  5068. },
  5069. _stripBreakerNodes: function(node){
  5070. // summary:
  5071. // Function for stripping out the breaker spans inserted by the formatting command.
  5072. // Registered as a filter for IE, handles the breaker spans needed to fix up
  5073. // How bold/italic/etc, work when selection is collapsed (single cursor).
  5074. win.withGlobal(this.window, lang.hitch(this, function(){
  5075. var breakers = query(".ieFormatBreakerSpan", node);
  5076. var i;
  5077. for(i = 0; i < breakers.length; i++){
  5078. var b = breakers[i];
  5079. while(b.firstChild){
  5080. domConstruct.place(b.firstChild, b, "before");
  5081. }
  5082. domConstruct.destroy(b);
  5083. }
  5084. }));
  5085. return node;
  5086. }
  5087. });
  5088. return BuxRichText;
  5089. });
  5090. },
  5091. 'dijit/_editor/plugins/TextColor':function(){
  5092. define("dijit/_editor/plugins/TextColor", [
  5093. "require",
  5094. "dojo/colors", // colors.fromRgb
  5095. "dojo/_base/declare", // declare
  5096. "dojo/_base/lang",
  5097. "../_Plugin",
  5098. "../../form/DropDownButton"
  5099. ], function(require, colors, declare, lang, _Plugin, DropDownButton){
  5100. /*=====
  5101. var _Plugin = dijit._editor._Plugin;
  5102. =====*/
  5103. // module:
  5104. // dijit/_editor/plugins/TextColor
  5105. // summary:
  5106. // This plugin provides dropdown color pickers for setting text color and background color
  5107. var TextColor = declare("dijit._editor.plugins.TextColor", _Plugin, {
  5108. // summary:
  5109. // This plugin provides dropdown color pickers for setting text color and background color
  5110. //
  5111. // description:
  5112. // The commands provided by this plugin are:
  5113. // * foreColor - sets the text color
  5114. // * hiliteColor - sets the background color
  5115. // Override _Plugin.buttonClass to use DropDownButton (with ColorPalette) to control this plugin
  5116. buttonClass: DropDownButton,
  5117. // useDefaultCommand: Boolean
  5118. // False as we do not use the default editor command/click behavior.
  5119. useDefaultCommand: false,
  5120. _initButton: function(){
  5121. this.inherited(arguments);
  5122. // Setup to lazy load ColorPalette first time the button is clicked
  5123. var self = this;
  5124. this.button.loadDropDown = function(callback){
  5125. require(["../../ColorPalette"], lang.hitch(this, function(ColorPalette){
  5126. this.dropDown = new ColorPalette({
  5127. value: self.value,
  5128. onChange: function(color){
  5129. self.editor.execCommand(self.command, color);
  5130. }
  5131. });
  5132. callback();
  5133. }));
  5134. };
  5135. },
  5136. updateState: function(){
  5137. // summary:
  5138. // Overrides _Plugin.updateState(). This updates the ColorPalette
  5139. // to show the color of the currently selected text.
  5140. // tags:
  5141. // protected
  5142. var _e = this.editor;
  5143. var _c = this.command;
  5144. if(!_e || !_e.isLoaded || !_c.length){
  5145. return;
  5146. }
  5147. if(this.button){
  5148. var disabled = this.get("disabled");
  5149. this.button.set("disabled", disabled);
  5150. if(disabled){ return; }
  5151. var value;
  5152. try{
  5153. value = _e.queryCommandValue(_c)|| "";
  5154. }catch(e){
  5155. //Firefox may throw error above if the editor is just loaded, ignore it
  5156. value = "";
  5157. }
  5158. }
  5159. if(value == ""){
  5160. value = "#000000";
  5161. }
  5162. if(value == "transparent"){
  5163. value = "#ffffff";
  5164. }
  5165. if(typeof value == "string"){
  5166. //if RGB value, convert to hex value
  5167. if(value.indexOf("rgb")> -1){
  5168. value = colors.fromRgb(value).toHex();
  5169. }
  5170. }else{ //it's an integer(IE returns an MS access #)
  5171. value =((value & 0x0000ff)<< 16)|(value & 0x00ff00)|((value & 0xff0000)>>> 16);
  5172. value = value.toString(16);
  5173. value = "#000000".slice(0, 7 - value.length)+ value;
  5174. }
  5175. this.value = value;
  5176. var dropDown = this.button.dropDown;
  5177. if(dropDown && value !== dropDown.get('value')){
  5178. dropDown.set('value', value, false);
  5179. }
  5180. }
  5181. });
  5182. // Register this plugin.
  5183. _Plugin.registry["foreColor"] = function(){
  5184. return new TextColor({command: "foreColor"});
  5185. };
  5186. _Plugin.registry["hiliteColor"] = function(){
  5187. return new TextColor({command: "hiliteColor"});
  5188. };
  5189. return TextColor;
  5190. });
  5191. },
  5192. 'dijit/_editor/selection':function(){
  5193. define("dijit/_editor/selection", [
  5194. "dojo/dom", // dom.byId
  5195. "dojo/_base/lang",
  5196. "dojo/_base/sniff", // has("ie") has("opera")
  5197. "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
  5198. ".." // for exporting symbols to dijit._editor.selection (TODO: remove in 2.0)
  5199. ], function(dom, lang, has, win, dijit){
  5200. // module:
  5201. // dijit/_editor/selection
  5202. // summary:
  5203. // Text selection API
  5204. lang.getObject("_editor.selection", true, dijit);
  5205. // FIXME:
  5206. // all of these methods branch internally for IE. This is probably
  5207. // sub-optimal in terms of runtime performance. We should investigate the
  5208. // size difference for differentiating at definition time.
  5209. lang.mixin(dijit._editor.selection, {
  5210. getType: function(){
  5211. // summary:
  5212. // Get the selection type (like win.doc.select.type in IE).
  5213. if(!win.doc.getSelection){
  5214. // IE6-8
  5215. return win.doc.selection.type.toLowerCase();
  5216. }else{
  5217. // W3C
  5218. var stype = "text";
  5219. // Check if the actual selection is a CONTROL (IMG, TABLE, HR, etc...).
  5220. var oSel;
  5221. try{
  5222. oSel = win.global.getSelection();
  5223. }catch(e){ /*squelch*/ }
  5224. if(oSel && oSel.rangeCount == 1){
  5225. var oRange = oSel.getRangeAt(0);
  5226. if( (oRange.startContainer == oRange.endContainer) &&
  5227. ((oRange.endOffset - oRange.startOffset) == 1) &&
  5228. (oRange.startContainer.nodeType != 3 /* text node*/)
  5229. ){
  5230. stype = "control";
  5231. }
  5232. }
  5233. return stype; //String
  5234. }
  5235. },
  5236. getSelectedText: function(){
  5237. // summary:
  5238. // Return the text (no html tags) included in the current selection or null if no text is selected
  5239. if(!win.doc.getSelection){
  5240. // IE6-8
  5241. if(dijit._editor.selection.getType() == 'control'){
  5242. return null;
  5243. }
  5244. return win.doc.selection.createRange().text;
  5245. }else{
  5246. // W3C
  5247. var selection = win.global.getSelection();
  5248. if(selection){
  5249. return selection.toString(); //String
  5250. }
  5251. }
  5252. return '';
  5253. },
  5254. getSelectedHtml: function(){
  5255. // summary:
  5256. // Return the html text of the current selection or null if unavailable
  5257. if(!win.doc.getSelection){
  5258. // IE6-8
  5259. if(dijit._editor.selection.getType() == 'control'){
  5260. return null;
  5261. }
  5262. return win.doc.selection.createRange().htmlText;
  5263. }else{
  5264. // W3C
  5265. var selection = win.global.getSelection();
  5266. if(selection && selection.rangeCount){
  5267. var i;
  5268. var html = "";
  5269. for(i = 0; i < selection.rangeCount; i++){
  5270. //Handle selections spanning ranges, such as Opera
  5271. var frag = selection.getRangeAt(i).cloneContents();
  5272. var div = win.doc.createElement("div");
  5273. div.appendChild(frag);
  5274. html += div.innerHTML;
  5275. }
  5276. return html; //String
  5277. }
  5278. return null;
  5279. }
  5280. },
  5281. getSelectedElement: function(){
  5282. // summary:
  5283. // Retrieves the selected element (if any), just in the case that
  5284. // a single element (object like and image or a table) is
  5285. // selected.
  5286. if(dijit._editor.selection.getType() == "control"){
  5287. if(!win.doc.getSelection){
  5288. // IE6-8
  5289. var range = win.doc.selection.createRange();
  5290. if(range && range.item){
  5291. return win.doc.selection.createRange().item(0);
  5292. }
  5293. }else{
  5294. // W3C
  5295. var selection = win.global.getSelection();
  5296. return selection.anchorNode.childNodes[ selection.anchorOffset ];
  5297. }
  5298. }
  5299. return null;
  5300. },
  5301. getParentElement: function(){
  5302. // summary:
  5303. // Get the parent element of the current selection
  5304. if(dijit._editor.selection.getType() == "control"){
  5305. var p = this.getSelectedElement();
  5306. if(p){ return p.parentNode; }
  5307. }else{
  5308. if(!win.doc.getSelection){
  5309. // IE6-8
  5310. var r = win.doc.selection.createRange();
  5311. r.collapse(true);
  5312. return r.parentElement();
  5313. }else{
  5314. // W3C
  5315. var selection = win.global.getSelection();
  5316. if(selection){
  5317. var node = selection.anchorNode;
  5318. while(node && (node.nodeType != 1)){ // not an element
  5319. node = node.parentNode;
  5320. }
  5321. return node;
  5322. }
  5323. }
  5324. }
  5325. return null;
  5326. },
  5327. hasAncestorElement: function(/*String*/tagName /* ... */){
  5328. // summary:
  5329. // Check whether current selection has a parent element which is
  5330. // of type tagName (or one of the other specified tagName)
  5331. // tagName: String
  5332. // The tag name to determine if it has an ancestor of.
  5333. return this.getAncestorElement.apply(this, arguments) != null; //Boolean
  5334. },
  5335. getAncestorElement: function(/*String*/tagName /* ... */){
  5336. // summary:
  5337. // Return the parent element of the current selection which is of
  5338. // type tagName (or one of the other specified tagName)
  5339. // tagName: String
  5340. // The tag name to determine if it has an ancestor of.
  5341. var node = this.getSelectedElement() || this.getParentElement();
  5342. return this.getParentOfType(node, arguments); //DOMNode
  5343. },
  5344. isTag: function(/*DomNode*/ node, /*String[]*/ tags){
  5345. // summary:
  5346. // Function to determine if a node is one of an array of tags.
  5347. // node:
  5348. // The node to inspect.
  5349. // tags:
  5350. // An array of tag name strings to check to see if the node matches.
  5351. if(node && node.tagName){
  5352. var _nlc = node.tagName.toLowerCase();
  5353. for(var i=0; i<tags.length; i++){
  5354. var _tlc = String(tags[i]).toLowerCase();
  5355. if(_nlc == _tlc){
  5356. return _tlc; // String
  5357. }
  5358. }
  5359. }
  5360. return "";
  5361. },
  5362. getParentOfType: function(/*DomNode*/ node, /*String[]*/ tags){
  5363. // summary:
  5364. // Function to locate a parent node that matches one of a set of tags
  5365. // node:
  5366. // The node to inspect.
  5367. // tags:
  5368. // An array of tag name strings to check to see if the node matches.
  5369. while(node){
  5370. if(this.isTag(node, tags).length){
  5371. return node; // DOMNode
  5372. }
  5373. node = node.parentNode;
  5374. }
  5375. return null;
  5376. },
  5377. collapse: function(/*Boolean*/beginning){
  5378. // summary:
  5379. // Function to collapse (clear), the current selection
  5380. // beginning: Boolean
  5381. // Boolean to indicate whether to collapse the cursor to the beginning of the selection or end.
  5382. if(window.getSelection){
  5383. var selection = win.global.getSelection();
  5384. if(selection.removeAllRanges){ // Mozilla
  5385. if(beginning){
  5386. selection.collapseToStart();
  5387. }else{
  5388. selection.collapseToEnd();
  5389. }
  5390. }else{ // Safari
  5391. // pulled from WebCore/ecma/kjs_window.cpp, line 2536
  5392. selection.collapse(beginning);
  5393. }
  5394. }else if(has("ie")){ // IE
  5395. var range = win.doc.selection.createRange();
  5396. range.collapse(beginning);
  5397. range.select();
  5398. }
  5399. },
  5400. remove: function(){
  5401. // summary:
  5402. // Function to delete the currently selected content from the document.
  5403. var sel = win.doc.selection;
  5404. if(!win.doc.getSelection){
  5405. // IE6-8
  5406. if(sel.type.toLowerCase() != "none"){
  5407. sel.clear();
  5408. }
  5409. return sel; //Selection
  5410. }else{
  5411. // W3C
  5412. sel = win.global.getSelection();
  5413. sel.deleteFromDocument();
  5414. return sel; //Selection
  5415. }
  5416. },
  5417. selectElementChildren: function(/*DomNode*/element,/*Boolean?*/nochangefocus){
  5418. // summary:
  5419. // clear previous selection and select the content of the node
  5420. // (excluding the node itself)
  5421. // element: DOMNode
  5422. // The element you wish to select the children content of.
  5423. // nochangefocus: Boolean
  5424. // Boolean to indicate if the foxus should change or not.
  5425. var global = win.global;
  5426. var doc = win.doc;
  5427. var range;
  5428. element = dom.byId(element);
  5429. if(doc.selection && !doc.getSelection && win.body().createTextRange){
  5430. // IE6-8
  5431. range = element.ownerDocument.body.createTextRange();
  5432. range.moveToElementText(element);
  5433. if(!nochangefocus){
  5434. try{
  5435. range.select(); // IE throws an exception here if the widget is hidden. See #5439
  5436. }catch(e){ /* squelch */}
  5437. }
  5438. }else if(global.getSelection){
  5439. // W3C
  5440. var selection = win.global.getSelection();
  5441. if(has("opera")){
  5442. //Opera's selectAllChildren doesn't seem to work right
  5443. //against <body> nodes and possibly others ... so
  5444. //we use the W3C range API
  5445. if(selection.rangeCount){
  5446. range = selection.getRangeAt(0);
  5447. }else{
  5448. range = doc.createRange();
  5449. }
  5450. range.setStart(element, 0);
  5451. range.setEnd(element,(element.nodeType == 3)?element.length:element.childNodes.length);
  5452. selection.addRange(range);
  5453. }else{
  5454. selection.selectAllChildren(element);
  5455. }
  5456. }
  5457. },
  5458. selectElement: function(/*DomNode*/element,/*Boolean?*/nochangefocus){
  5459. // summary:
  5460. // clear previous selection and select element (including all its children)
  5461. // element: DOMNode
  5462. // The element to select.
  5463. // nochangefocus: Boolean
  5464. // Boolean indicating if the focus should be changed. IE only.
  5465. var range;
  5466. var doc = win.doc;
  5467. var global = win.global;
  5468. element = dom.byId(element);
  5469. if(!doc.getSelection && win.body().createTextRange){
  5470. // IE6-8
  5471. try{
  5472. var tg = element.tagName ? element.tagName.toLowerCase() : "";
  5473. if(tg === "img" || tg === "table"){
  5474. range = win.body().createControlRange();
  5475. }else{
  5476. range = win.body().createRange();
  5477. }
  5478. range.addElement(element);
  5479. if(!nochangefocus){
  5480. range.select();
  5481. }
  5482. }catch(e){
  5483. this.selectElementChildren(element,nochangefocus);
  5484. }
  5485. }else if(global.getSelection){
  5486. // W3C
  5487. var selection = global.getSelection();
  5488. range = doc.createRange();
  5489. if(selection.removeAllRanges){ // Mozilla
  5490. // FIXME: does this work on Safari?
  5491. if(has("opera")){
  5492. //Opera works if you use the current range on
  5493. //the selection if present.
  5494. if(selection.getRangeAt(0)){
  5495. range = selection.getRangeAt(0);
  5496. }
  5497. }
  5498. range.selectNode(element);
  5499. selection.removeAllRanges();
  5500. selection.addRange(range);
  5501. }
  5502. }
  5503. },
  5504. inSelection: function(node){
  5505. // summary:
  5506. // This function determines if 'node' is
  5507. // in the current selection.
  5508. // tags:
  5509. // public
  5510. if(node){
  5511. var newRange;
  5512. var doc = win.doc;
  5513. var range;
  5514. if(win.global.getSelection){
  5515. //WC3
  5516. var sel = win.global.getSelection();
  5517. if(sel && sel.rangeCount > 0){
  5518. range = sel.getRangeAt(0);
  5519. }
  5520. if(range && range.compareBoundaryPoints && doc.createRange){
  5521. try{
  5522. newRange = doc.createRange();
  5523. newRange.setStart(node, 0);
  5524. if(range.compareBoundaryPoints(range.START_TO_END, newRange) === 1){
  5525. return true;
  5526. }
  5527. }catch(e){ /* squelch */}
  5528. }
  5529. }else if(doc.selection){
  5530. // Probably IE, so we can't use the range object as the pseudo
  5531. // range doesn't implement the boundry checking, we have to
  5532. // use IE specific crud.
  5533. range = doc.selection.createRange();
  5534. try{
  5535. newRange = node.ownerDocument.body.createControlRange();
  5536. if(newRange){
  5537. newRange.addElement(node);
  5538. }
  5539. }catch(e1){
  5540. try{
  5541. newRange = node.ownerDocument.body.createTextRange();
  5542. newRange.moveToElementText(node);
  5543. }catch(e2){/* squelch */}
  5544. }
  5545. if(range && newRange){
  5546. // We can finally compare similar to W3C
  5547. if(range.compareEndPoints("EndToStart", newRange) === 1){
  5548. return true;
  5549. }
  5550. }
  5551. }
  5552. }
  5553. return false; // boolean
  5554. }
  5555. });
  5556. return dijit._editor.selection;
  5557. });
  5558. },
  5559. 'dijit/_editor/range':function(){
  5560. define("dijit/_editor/range", [
  5561. "dojo/_base/array", // array.every
  5562. "dojo/_base/declare", // declare
  5563. "dojo/_base/lang", // lang.isArray
  5564. "dojo/_base/window", // win.global
  5565. ".." // for exporting symbols to dijit, TODO: remove in 2.0
  5566. ], function(array, declare, lang, win, dijit){
  5567. // module:
  5568. // dijit/_editor/range
  5569. // summary:
  5570. // W3C range API
  5571. dijit.range={};
  5572. dijit.range.getIndex = function(/*DomNode*/node, /*DomNode*/parent){
  5573. // dojo.profile.start("dijit.range.getIndex");
  5574. var ret = [], retR = [];
  5575. var onode = node;
  5576. var pnode, n;
  5577. while(node != parent){
  5578. var i = 0;
  5579. pnode = node.parentNode;
  5580. while((n = pnode.childNodes[i++])){
  5581. if(n === node){
  5582. --i;
  5583. break;
  5584. }
  5585. }
  5586. //if(i>=pnode.childNodes.length){
  5587. //dojo.debug("Error finding index of a node in dijit.range.getIndex");
  5588. //}
  5589. ret.unshift(i);
  5590. retR.unshift(i - pnode.childNodes.length);
  5591. node = pnode;
  5592. }
  5593. //normalized() can not be called so often to prevent
  5594. //invalidating selection/range, so we have to detect
  5595. //here that any text nodes in a row
  5596. if(ret.length > 0 && onode.nodeType == 3){
  5597. n = onode.previousSibling;
  5598. while(n && n.nodeType == 3){
  5599. ret[ret.length - 1]--;
  5600. n = n.previousSibling;
  5601. }
  5602. n = onode.nextSibling;
  5603. while(n && n.nodeType == 3){
  5604. retR[retR.length - 1]++;
  5605. n = n.nextSibling;
  5606. }
  5607. }
  5608. // dojo.profile.end("dijit.range.getIndex");
  5609. return {o: ret, r:retR};
  5610. };
  5611. dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
  5612. if(!lang.isArray(index) || index.length == 0){
  5613. return parent;
  5614. }
  5615. var node = parent;
  5616. // if(!node)debugger
  5617. array.every(index, function(i){
  5618. if(i >= 0 && i < node.childNodes.length){
  5619. node = node.childNodes[i];
  5620. }else{
  5621. node = null;
  5622. //console.debug('Error: can not find node with index',index,'under parent node',parent );
  5623. return false; //terminate array.every
  5624. }
  5625. return true; //carry on the every loop
  5626. });
  5627. return node;
  5628. };
  5629. dijit.range.getCommonAncestor = function(n1, n2, root){
  5630. root = root || n1.ownerDocument.body;
  5631. var getAncestors = function(n){
  5632. var as = [];
  5633. while(n){
  5634. as.unshift(n);
  5635. if(n !== root){
  5636. n = n.parentNode;
  5637. }else{
  5638. break;
  5639. }
  5640. }
  5641. return as;
  5642. };
  5643. var n1as = getAncestors(n1);
  5644. var n2as = getAncestors(n2);
  5645. var m = Math.min(n1as.length, n2as.length);
  5646. var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
  5647. for(var i = 1; i < m; i++){
  5648. if(n1as[i] === n2as[i]){
  5649. com = n1as[i]
  5650. }else{
  5651. break;
  5652. }
  5653. }
  5654. return com;
  5655. };
  5656. dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
  5657. root = root || node.ownerDocument.body;
  5658. while(node && node !== root){
  5659. var name = node.nodeName.toUpperCase();
  5660. if(regex.test(name)){
  5661. return node;
  5662. }
  5663. node = node.parentNode;
  5664. }
  5665. return null;
  5666. };
  5667. dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
  5668. dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
  5669. root = root || node.ownerDocument.body;
  5670. regex = regex || dijit.range.BlockTagNames;
  5671. var block = null, blockContainer;
  5672. while(node && node !== root){
  5673. var name = node.nodeName.toUpperCase();
  5674. if(!block && regex.test(name)){
  5675. block = node;
  5676. }
  5677. if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
  5678. blockContainer = node;
  5679. }
  5680. node = node.parentNode;
  5681. }
  5682. return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
  5683. };
  5684. dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
  5685. var atBeginning = false;
  5686. var offsetAtBeginning = (offset == 0);
  5687. if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space
  5688. if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0, offset))){
  5689. offsetAtBeginning = true;
  5690. }
  5691. }
  5692. if(offsetAtBeginning){
  5693. var cnode = node;
  5694. atBeginning = true;
  5695. while(cnode && cnode !== container){
  5696. if(cnode.previousSibling){
  5697. atBeginning = false;
  5698. break;
  5699. }
  5700. cnode = cnode.parentNode;
  5701. }
  5702. }
  5703. return atBeginning;
  5704. };
  5705. dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
  5706. var atEnd = false;
  5707. var offsetAtEnd = (offset == (node.length || node.childNodes.length));
  5708. if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space
  5709. if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){
  5710. offsetAtEnd = true;
  5711. }
  5712. }
  5713. if(offsetAtEnd){
  5714. var cnode = node;
  5715. atEnd = true;
  5716. while(cnode && cnode !== container){
  5717. if(cnode.nextSibling){
  5718. atEnd = false;
  5719. break;
  5720. }
  5721. cnode = cnode.parentNode;
  5722. }
  5723. }
  5724. return atEnd;
  5725. };
  5726. dijit.range.adjacentNoneTextNode = function(startnode, next){
  5727. var node = startnode;
  5728. var len = (0 - startnode.length) || 0;
  5729. var prop = next ? 'nextSibling' : 'previousSibling';
  5730. while(node){
  5731. if(node.nodeType != 3){
  5732. break;
  5733. }
  5734. len += node.length;
  5735. node = node[prop];
  5736. }
  5737. return [node,len];
  5738. };
  5739. dijit.range._w3c = Boolean(window['getSelection']);
  5740. dijit.range.create = function(/*Window?*/window){
  5741. if(dijit.range._w3c){
  5742. return (window || win.global).document.createRange();
  5743. }else{//IE
  5744. return new dijit.range.W3CRange;
  5745. }
  5746. };
  5747. dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){
  5748. if(dijit.range._w3c){
  5749. return win.getSelection();
  5750. }else{//IE
  5751. var s = new dijit.range.ie.selection(win);
  5752. if(!ignoreUpdate){
  5753. s._getCurrentSelection();
  5754. }
  5755. return s;
  5756. }
  5757. };
  5758. if(!dijit.range._w3c){
  5759. dijit.range.ie = {
  5760. cachedSelection: {},
  5761. selection: function(win){
  5762. this._ranges = [];
  5763. this.addRange = function(r, /*boolean*/internal){
  5764. this._ranges.push(r);
  5765. if(!internal){
  5766. r._select();
  5767. }
  5768. this.rangeCount = this._ranges.length;
  5769. };
  5770. this.removeAllRanges = function(){
  5771. //don't detach, the range may be used later
  5772. // for(var i=0;i<this._ranges.length;i++){
  5773. // this._ranges[i].detach();
  5774. // }
  5775. this._ranges = [];
  5776. this.rangeCount = 0;
  5777. };
  5778. var _initCurrentRange = function(){
  5779. var r = win.document.selection.createRange();
  5780. var type = win.document.selection.type.toUpperCase();
  5781. if(type == "CONTROL"){
  5782. //TODO: multiple range selection(?)
  5783. return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
  5784. }else{
  5785. return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
  5786. }
  5787. };
  5788. this.getRangeAt = function(i){
  5789. return this._ranges[i];
  5790. };
  5791. this._getCurrentSelection = function(){
  5792. this.removeAllRanges();
  5793. var r = _initCurrentRange();
  5794. if(r){
  5795. this.addRange(r, true);
  5796. this.isCollapsed = r.collapsed;
  5797. }else{
  5798. this.isCollapsed = true;
  5799. }
  5800. };
  5801. },
  5802. decomposeControlRange: function(range){
  5803. var firstnode = range.item(0), lastnode = range.item(range.length - 1);
  5804. var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
  5805. var startOffset = dijit.range.getIndex(firstnode, startContainer).o[0];
  5806. var endOffset = dijit.range.getIndex(lastnode, endContainer).o[0] + 1;
  5807. return [startContainer, startOffset,endContainer, endOffset];
  5808. },
  5809. getEndPoint: function(range, end){
  5810. var atmrange = range.duplicate();
  5811. atmrange.collapse(!end);
  5812. var cmpstr = 'EndTo' + (end ? 'End' : 'Start');
  5813. var parentNode = atmrange.parentElement();
  5814. var startnode, startOffset, lastNode;
  5815. if(parentNode.childNodes.length > 0){
  5816. array.every(parentNode.childNodes, function(node, i){
  5817. var calOffset;
  5818. if(node.nodeType != 3){
  5819. atmrange.moveToElementText(node);
  5820. if(atmrange.compareEndPoints(cmpstr, range) > 0){
  5821. //startnode = node.previousSibling;
  5822. if(lastNode && lastNode.nodeType == 3){
  5823. //where shall we put the start? in the text node or after?
  5824. startnode = lastNode;
  5825. calOffset = true;
  5826. }else{
  5827. startnode = parentNode;
  5828. startOffset = i;
  5829. return false;
  5830. }
  5831. }else{
  5832. if(i == parentNode.childNodes.length - 1){
  5833. startnode = parentNode;
  5834. startOffset = parentNode.childNodes.length;
  5835. return false;
  5836. }
  5837. }
  5838. }else{
  5839. if(i == parentNode.childNodes.length - 1){//at the end of this node
  5840. startnode = node;
  5841. calOffset = true;
  5842. }
  5843. }
  5844. // try{
  5845. if(calOffset && startnode){
  5846. var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
  5847. if(prevnode){
  5848. startnode = prevnode.nextSibling;
  5849. }else{
  5850. startnode = parentNode.firstChild; //firstChild must be a text node
  5851. }
  5852. var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
  5853. prevnode = prevnodeobj[0];
  5854. var lenoffset = prevnodeobj[1];
  5855. if(prevnode){
  5856. atmrange.moveToElementText(prevnode);
  5857. atmrange.collapse(false);
  5858. }else{
  5859. atmrange.moveToElementText(parentNode);
  5860. }
  5861. atmrange.setEndPoint(cmpstr, range);
  5862. startOffset = atmrange.text.length - lenoffset;
  5863. return false;
  5864. }
  5865. // }catch(e){ debugger }
  5866. lastNode = node;
  5867. return true;
  5868. });
  5869. }else{
  5870. startnode = parentNode;
  5871. startOffset = 0;
  5872. }
  5873. //if at the end of startnode and we are dealing with start container, then
  5874. //move the startnode to nextSibling if it is a text node
  5875. //TODO: do this for end container?
  5876. if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){
  5877. var nextnode = startnode.nextSibling;
  5878. if(nextnode && nextnode.nodeType == 3){
  5879. startnode = nextnode;
  5880. startOffset = 0;
  5881. }
  5882. }
  5883. return [startnode, startOffset];
  5884. },
  5885. setEndPoint: function(range, container, offset){
  5886. //text node
  5887. var atmrange = range.duplicate(), node, len;
  5888. if(container.nodeType != 3){ //normal node
  5889. if(offset > 0){
  5890. node = container.childNodes[offset - 1];
  5891. if(node){
  5892. if(node.nodeType == 3){
  5893. container = node;
  5894. offset = node.length;
  5895. //pass through
  5896. }else{
  5897. if(node.nextSibling && node.nextSibling.nodeType == 3){
  5898. container = node.nextSibling;
  5899. offset = 0;
  5900. //pass through
  5901. }else{
  5902. atmrange.moveToElementText(node.nextSibling ? node : container);
  5903. var parent = node.parentNode;
  5904. var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling);
  5905. atmrange.collapse(false);
  5906. parent.removeChild(tempNode);
  5907. }
  5908. }
  5909. }
  5910. }else{
  5911. atmrange.moveToElementText(container);
  5912. atmrange.collapse(true);
  5913. }
  5914. }
  5915. if(container.nodeType == 3){
  5916. var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
  5917. var prevnode = prevnodeobj[0];
  5918. len = prevnodeobj[1];
  5919. if(prevnode){
  5920. atmrange.moveToElementText(prevnode);
  5921. atmrange.collapse(false);
  5922. //if contentEditable is not inherit, the above collapse won't make the end point
  5923. //in the correctly position: it always has a -1 offset, so compensate it
  5924. if(prevnode.contentEditable != 'inherit'){
  5925. len++;
  5926. }
  5927. }else{
  5928. atmrange.moveToElementText(container.parentNode);
  5929. atmrange.collapse(true);
  5930. }
  5931. offset += len;
  5932. if(offset > 0){
  5933. if(atmrange.move('character', offset) != offset){
  5934. console.error('Error when moving!');
  5935. }
  5936. }
  5937. }
  5938. return atmrange;
  5939. },
  5940. decomposeTextRange: function(range){
  5941. var tmpary = dijit.range.ie.getEndPoint(range);
  5942. var startContainer = tmpary[0], startOffset = tmpary[1];
  5943. var endContainer = tmpary[0], endOffset = tmpary[1];
  5944. if(range.htmlText.length){
  5945. if(range.htmlText == range.text){ //in the same text node
  5946. endOffset = startOffset + range.text.length;
  5947. }else{
  5948. tmpary = dijit.range.ie.getEndPoint(range, true);
  5949. endContainer = tmpary[0],endOffset = tmpary[1];
  5950. // if(startContainer.tagName == "BODY"){
  5951. // startContainer = startContainer.firstChild;
  5952. // }
  5953. }
  5954. }
  5955. return [startContainer, startOffset, endContainer, endOffset];
  5956. },
  5957. setRange: function(range, startContainer, startOffset, endContainer, endOffset, collapsed){
  5958. var start = dijit.range.ie.setEndPoint(range, startContainer, startOffset);
  5959. range.setEndPoint('StartToStart', start);
  5960. if(!collapsed){
  5961. var end = dijit.range.ie.setEndPoint(range, endContainer, endOffset);
  5962. }
  5963. range.setEndPoint('EndToEnd', end || start);
  5964. return range;
  5965. }
  5966. };
  5967. declare("dijit.range.W3CRange",null, {
  5968. constructor: function(){
  5969. if(arguments.length>0){
  5970. this.setStart(arguments[0][0],arguments[0][1]);
  5971. this.setEnd(arguments[0][2],arguments[0][3]);
  5972. }else{
  5973. this.commonAncestorContainer = null;
  5974. this.startContainer = null;
  5975. this.startOffset = 0;
  5976. this.endContainer = null;
  5977. this.endOffset = 0;
  5978. this.collapsed = true;
  5979. }
  5980. },
  5981. _updateInternal: function(){
  5982. if(this.startContainer !== this.endContainer){
  5983. this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer);
  5984. }else{
  5985. this.commonAncestorContainer = this.startContainer;
  5986. }
  5987. this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
  5988. },
  5989. setStart: function(node, offset){
  5990. offset=parseInt(offset);
  5991. if(this.startContainer === node && this.startOffset == offset){
  5992. return;
  5993. }
  5994. delete this._cachedBookmark;
  5995. this.startContainer = node;
  5996. this.startOffset = offset;
  5997. if(!this.endContainer){
  5998. this.setEnd(node, offset);
  5999. }else{
  6000. this._updateInternal();
  6001. }
  6002. },
  6003. setEnd: function(node, offset){
  6004. offset=parseInt(offset);
  6005. if(this.endContainer === node && this.endOffset == offset){
  6006. return;
  6007. }
  6008. delete this._cachedBookmark;
  6009. this.endContainer = node;
  6010. this.endOffset = offset;
  6011. if(!this.startContainer){
  6012. this.setStart(node, offset);
  6013. }else{
  6014. this._updateInternal();
  6015. }
  6016. },
  6017. setStartAfter: function(node, offset){
  6018. this._setPoint('setStart', node, offset, 1);
  6019. },
  6020. setStartBefore: function(node, offset){
  6021. this._setPoint('setStart', node, offset, 0);
  6022. },
  6023. setEndAfter: function(node, offset){
  6024. this._setPoint('setEnd', node, offset, 1);
  6025. },
  6026. setEndBefore: function(node, offset){
  6027. this._setPoint('setEnd', node, offset, 0);
  6028. },
  6029. _setPoint: function(what, node, offset, ext){
  6030. var index = dijit.range.getIndex(node, node.parentNode).o;
  6031. this[what](node.parentNode, index.pop()+ext);
  6032. },
  6033. _getIERange: function(){
  6034. var r = (this._body || this.endContainer.ownerDocument.body).createTextRange();
  6035. dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed);
  6036. return r;
  6037. },
  6038. getBookmark: function(){
  6039. this._getIERange();
  6040. return this._cachedBookmark;
  6041. },
  6042. _select: function(){
  6043. var r = this._getIERange();
  6044. r.select();
  6045. },
  6046. deleteContents: function(){
  6047. var s = this.startContainer, r = this._getIERange();
  6048. if(s.nodeType === 3 && !this.startOffset){
  6049. //if the range starts at the beginning of a
  6050. //text node, move it to before the textnode
  6051. //to make sure the range is still valid
  6052. //after deleteContents() finishes
  6053. this.setStartBefore(s);
  6054. }
  6055. r.pasteHTML('');
  6056. this.endContainer = this.startContainer;
  6057. this.endOffset = this.startOffset;
  6058. this.collapsed = true;
  6059. },
  6060. cloneRange: function(){
  6061. var r = new dijit.range.W3CRange([this.startContainer,this.startOffset,
  6062. this.endContainer,this.endOffset]);
  6063. r._body = this._body;
  6064. return r;
  6065. },
  6066. detach: function(){
  6067. this._body = null;
  6068. this.commonAncestorContainer = null;
  6069. this.startContainer = null;
  6070. this.startOffset = 0;
  6071. this.endContainer = null;
  6072. this.endOffset = 0;
  6073. this.collapsed = true;
  6074. }
  6075. });
  6076. } //if(!dijit.range._w3c)
  6077. return dijit.range;
  6078. });
  6079. },
  6080. '*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"]']);}
  6081. }});
  6082. define("dojo/buxTextEditor", [], 1);