mathquill.test.js 305 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177
  1. /**
  2. * MathQuill v0.10.1 http://mathquill.com
  3. * by Han, Jeanine, and Mary maintainers@mathquill.com
  4. *
  5. * This Source Code Form is subject to the terms of the
  6. * Mozilla Public License, v. 2.0. If a copy of the MPL
  7. * was not distributed with this file, You can obtain
  8. * one at http://mozilla.org/MPL/2.0/.
  9. */
  10. (function() {
  11. var jQuery = window.jQuery,
  12. undefined,
  13. mqCmdId = 'mathquill-command-id',
  14. mqBlockId = 'mathquill-block-id',
  15. min = Math.min,
  16. max = Math.max;
  17. function noop() {}
  18. /**
  19. * A utility higher-order function that makes defining variadic
  20. * functions more convenient by letting you essentially define functions
  21. * with the last argument as a splat, i.e. the last argument "gathers up"
  22. * remaining arguments to the function:
  23. * var doStuff = variadic(function(first, rest) { return rest; });
  24. * doStuff(1, 2, 3); // => [2, 3]
  25. */
  26. var __slice = [].slice;
  27. function variadic(fn) {
  28. var numFixedArgs = fn.length - 1;
  29. return function() {
  30. var args = __slice.call(arguments, 0, numFixedArgs);
  31. var varArg = __slice.call(arguments, numFixedArgs);
  32. return fn.apply(this, args.concat([ varArg ]));
  33. };
  34. }
  35. /**
  36. * A utility higher-order function that makes combining object-oriented
  37. * programming and functional programming techniques more convenient:
  38. * given a method name and any number of arguments to be bound, returns
  39. * a function that calls it's first argument's method of that name (if
  40. * it exists) with the bound arguments and any additional arguments that
  41. * are passed:
  42. * var sendMethod = send('method', 1, 2);
  43. * var obj = { method: function() { return Array.apply(this, arguments); } };
  44. * sendMethod(obj, 3, 4); // => [1, 2, 3, 4]
  45. * // or more specifically,
  46. * var obj2 = { method: function(one, two, three) { return one*two + three; } };
  47. * sendMethod(obj2, 3); // => 5
  48. * sendMethod(obj2, 4); // => 6
  49. */
  50. var send = variadic(function(method, args) {
  51. return variadic(function(obj, moreArgs) {
  52. if (method in obj) return obj[method].apply(obj, args.concat(moreArgs));
  53. });
  54. });
  55. /**
  56. * A utility higher-order function that creates "implicit iterators"
  57. * from "generators": given a function that takes in a sole argument,
  58. * a "yield_" function, that calls "yield_" repeatedly with an object as
  59. * a sole argument (presumably objects being iterated over), returns
  60. * a function that calls it's first argument on each of those objects
  61. * (if the first argument is a function, it is called repeatedly with
  62. * each object as the first argument, otherwise it is stringified and
  63. * the method of that name is called on each object (if such a method
  64. * exists)), passing along all additional arguments:
  65. * var a = [
  66. * { method: function(list) { list.push(1); } },
  67. * { method: function(list) { list.push(2); } },
  68. * { method: function(list) { list.push(3); } }
  69. * ];
  70. * a.each = iterator(function(yield_) {
  71. * for (var i in this) yield_(this[i]);
  72. * });
  73. * var list = [];
  74. * a.each('method', list);
  75. * list; // => [1, 2, 3]
  76. * // Note that the for-in loop will yield 'each', but 'each' maps to
  77. * // the function object created by iterator() which does not have a
  78. * // .method() method, so that just fails silently.
  79. */
  80. function iterator(generator) {
  81. return variadic(function(fn, args) {
  82. if (typeof fn !== 'function') fn = send(fn);
  83. var yield_ = function(obj) { return fn.apply(obj, [ obj ].concat(args)); };
  84. return generator.call(this, yield_);
  85. });
  86. }
  87. /**
  88. * sugar to make defining lots of commands easier.
  89. * TODO: rethink this.
  90. */
  91. function bind(cons /*, args... */) {
  92. var args = __slice.call(arguments, 1);
  93. return function() {
  94. return cons.apply(this, args);
  95. };
  96. }
  97. /**
  98. * a development-only debug method. This definition and all
  99. * calls to `pray` will be stripped from the minified
  100. * build of mathquill.
  101. *
  102. * This function must be called by name to be removed
  103. * at compile time. Do not define another function
  104. * with the same name, and only call this function by
  105. * name.
  106. */
  107. function pray(message, cond) {
  108. if (!cond) throw new Error('prayer failed: '+message);
  109. }
  110. var P = (function(prototype, ownProperty, undefined) {
  111. // helper functions that also help minification
  112. function isObject(o) { return typeof o === 'object'; }
  113. function isFunction(f) { return typeof f === 'function'; }
  114. // used to extend the prototypes of superclasses (which might not
  115. // have `.Bare`s)
  116. function SuperclassBare() {}
  117. return function P(_superclass /* = Object */, definition) {
  118. // handle the case where no superclass is given
  119. if (definition === undefined) {
  120. definition = _superclass;
  121. _superclass = Object;
  122. }
  123. // C is the class to be returned.
  124. //
  125. // It delegates to instantiating an instance of `Bare`, so that it
  126. // will always return a new instance regardless of the calling
  127. // context.
  128. //
  129. // TODO: the Chrome inspector shows all created objects as `C`
  130. // rather than `Object`. Setting the .name property seems to
  131. // have no effect. Is there a way to override this behavior?
  132. function C() {
  133. var self = new Bare;
  134. if (isFunction(self.init)) self.init.apply(self, arguments);
  135. return self;
  136. }
  137. // C.Bare is a class with a noop constructor. Its prototype is the
  138. // same as C, so that instances of C.Bare are also instances of C.
  139. // New objects can be allocated without initialization by calling
  140. // `new MyClass.Bare`.
  141. function Bare() {}
  142. C.Bare = Bare;
  143. // Set up the prototype of the new class.
  144. var _super = SuperclassBare[prototype] = _superclass[prototype];
  145. var proto = Bare[prototype] = C[prototype] = C.p = new SuperclassBare;
  146. // other variables, as a minifier optimization
  147. var extensions;
  148. // set the constructor property on the prototype, for convenience
  149. proto.constructor = C;
  150. C.mixin = function(def) {
  151. Bare[prototype] = C[prototype] = P(C, def)[prototype];
  152. return C;
  153. }
  154. return (C.open = function(def) {
  155. extensions = {};
  156. if (isFunction(def)) {
  157. // call the defining function with all the arguments you need
  158. // extensions captures the return value.
  159. extensions = def.call(C, proto, _super, C, _superclass);
  160. }
  161. else if (isObject(def)) {
  162. // if you passed an object instead, we'll take it
  163. extensions = def;
  164. }
  165. // ...and extend it
  166. if (isObject(extensions)) {
  167. for (var ext in extensions) {
  168. if (ownProperty.call(extensions, ext)) {
  169. proto[ext] = extensions[ext];
  170. }
  171. }
  172. }
  173. // if there's no init, we assume we're inheriting a non-pjs class, so
  174. // we default to applying the superclass's constructor.
  175. if (!isFunction(proto.init)) {
  176. proto.init = _superclass;
  177. }
  178. return C;
  179. })(definition);
  180. }
  181. // as a minifier optimization, we've closured in a few helper functions
  182. // and the string 'prototype' (C[p] is much shorter than C.prototype)
  183. })('prototype', ({}).hasOwnProperty);
  184. /*************************************************
  185. * Base classes of edit tree-related objects
  186. *
  187. * Only doing tree node manipulation via these
  188. * adopt/ disown methods guarantees well-formedness
  189. * of the tree.
  190. ************************************************/
  191. // L = 'left'
  192. // R = 'right'
  193. //
  194. // the contract is that they can be used as object properties
  195. // and (-L) === R, and (-R) === L.
  196. var L = -1;
  197. var R = 1;
  198. function prayDirection(dir) {
  199. pray('a direction was passed', dir === L || dir === R);
  200. }
  201. /**
  202. * Tiny extension of jQuery adding directionalized DOM manipulation methods.
  203. *
  204. * Funny how Pjs v3 almost just works with `jQuery.fn.init`.
  205. *
  206. * jQuery features that don't work on $:
  207. * - jQuery.*, like jQuery.ajax, obviously (Pjs doesn't and shouldn't
  208. * copy constructor properties)
  209. *
  210. * - jQuery(function), the shortcut for `jQuery(document).ready(function)`,
  211. * because `jQuery.fn.init` is idiosyncratic and Pjs doing, essentially,
  212. * `jQuery.fn.init.apply(this, arguments)` isn't quite right, you need:
  213. *
  214. * _.init = function(s, c) { jQuery.fn.init.call(this, s, c, $(document)); };
  215. *
  216. * if you actually give a shit (really, don't bother),
  217. * see https://github.com/jquery/jquery/blob/1.7.2/src/core.js#L889
  218. *
  219. * - jQuery(selector), because jQuery translates that to
  220. * `jQuery(document).find(selector)`, but Pjs doesn't (should it?) let
  221. * you override the result of a constructor call
  222. * + note that because of the jQuery(document) shortcut-ness, there's also
  223. * the 3rd-argument-needs-to-be-`$(document)` thing above, but the fix
  224. * for that (as can be seen above) is really easy. This problem requires
  225. * a way more intrusive fix
  226. *
  227. * And that's it! Everything else just magically works because jQuery internally
  228. * uses `this.constructor()` everywhere (hence calling `$`), but never ever does
  229. * `this.constructor.find` or anything like that, always doing `jQuery.find`.
  230. */
  231. var $ = P(jQuery, function(_) {
  232. _.insDirOf = function(dir, el) {
  233. return dir === L ?
  234. this.insertBefore(el.first()) : this.insertAfter(el.last());
  235. };
  236. _.insAtDirEnd = function(dir, el) {
  237. return dir === L ? this.prependTo(el) : this.appendTo(el);
  238. };
  239. });
  240. var Point = P(function(_) {
  241. _.parent = 0;
  242. _[L] = 0;
  243. _[R] = 0;
  244. _.init = function(parent, leftward, rightward) {
  245. this.parent = parent;
  246. this[L] = leftward;
  247. this[R] = rightward;
  248. };
  249. this.copy = function(pt) {
  250. return Point(pt.parent, pt[L], pt[R]);
  251. };
  252. });
  253. /**
  254. * MathQuill virtual-DOM tree-node abstract base class
  255. */
  256. var Node = P(function(_) {
  257. _[L] = 0;
  258. _[R] = 0
  259. _.parent = 0;
  260. var id = 0;
  261. function uniqueNodeId() { return id += 1; }
  262. this.byId = {};
  263. _.init = function() {
  264. this.id = uniqueNodeId();
  265. Node.byId[this.id] = this;
  266. this.ends = {};
  267. this.ends[L] = 0;
  268. this.ends[R] = 0;
  269. };
  270. _.dispose = function() { delete Node.byId[this.id]; };
  271. _.toString = function() { return '{{ MathQuill Node #'+this.id+' }}'; };
  272. _.jQ = $();
  273. _.jQadd = function(jQ) { return this.jQ = this.jQ.add(jQ); };
  274. _.jQize = function(jQ) {
  275. // jQuery-ifies this.html() and links up the .jQ of all corresponding Nodes
  276. var jQ = $(jQ || this.html());
  277. function jQadd(el) {
  278. if (el.getAttribute) {
  279. var cmdId = el.getAttribute('mathquill-command-id');
  280. var blockId = el.getAttribute('mathquill-block-id');
  281. if (cmdId) Node.byId[cmdId].jQadd(el);
  282. if (blockId) Node.byId[blockId].jQadd(el);
  283. }
  284. for (el = el.firstChild; el; el = el.nextSibling) {
  285. jQadd(el);
  286. }
  287. }
  288. for (var i = 0; i < jQ.length; i += 1) jQadd(jQ[i]);
  289. return jQ;
  290. };
  291. _.createDir = function(dir, cursor) {
  292. prayDirection(dir);
  293. var node = this;
  294. node.jQize();
  295. node.jQ.insDirOf(dir, cursor.jQ);
  296. cursor[dir] = node.adopt(cursor.parent, cursor[L], cursor[R]);
  297. return node;
  298. };
  299. _.createLeftOf = function(el) { return this.createDir(L, el); };
  300. _.selectChildren = function(leftEnd, rightEnd) {
  301. return Selection(leftEnd, rightEnd);
  302. };
  303. _.bubble = iterator(function(yield_) {
  304. for (var ancestor = this; ancestor; ancestor = ancestor.parent) {
  305. var result = yield_(ancestor);
  306. if (result === false) break;
  307. }
  308. return this;
  309. });
  310. _.postOrder = iterator(function(yield_) {
  311. (function recurse(descendant) {
  312. descendant.eachChild(recurse);
  313. yield_(descendant);
  314. })(this);
  315. return this;
  316. });
  317. _.isEmpty = function() {
  318. return this.ends[L] === 0 && this.ends[R] === 0;
  319. };
  320. _.children = function() {
  321. return Fragment(this.ends[L], this.ends[R]);
  322. };
  323. _.eachChild = function() {
  324. var children = this.children();
  325. children.each.apply(children, arguments);
  326. return this;
  327. };
  328. _.foldChildren = function(fold, fn) {
  329. return this.children().fold(fold, fn);
  330. };
  331. _.withDirAdopt = function(dir, parent, withDir, oppDir) {
  332. Fragment(this, this).withDirAdopt(dir, parent, withDir, oppDir);
  333. return this;
  334. };
  335. _.adopt = function(parent, leftward, rightward) {
  336. Fragment(this, this).adopt(parent, leftward, rightward);
  337. return this;
  338. };
  339. _.disown = function() {
  340. Fragment(this, this).disown();
  341. return this;
  342. };
  343. _.remove = function() {
  344. this.jQ.remove();
  345. this.postOrder('dispose');
  346. return this.disown();
  347. };
  348. });
  349. function prayWellFormed(parent, leftward, rightward) {
  350. pray('a parent is always present', parent);
  351. pray('leftward is properly set up', (function() {
  352. // either it's empty and `rightward` is the left end child (possibly empty)
  353. if (!leftward) return parent.ends[L] === rightward;
  354. // or it's there and its [R] and .parent are properly set up
  355. return leftward[R] === rightward && leftward.parent === parent;
  356. })());
  357. pray('rightward is properly set up', (function() {
  358. // either it's empty and `leftward` is the right end child (possibly empty)
  359. if (!rightward) return parent.ends[R] === leftward;
  360. // or it's there and its [L] and .parent are properly set up
  361. return rightward[L] === leftward && rightward.parent === parent;
  362. })());
  363. }
  364. /**
  365. * An entity outside the virtual tree with one-way pointers (so it's only a
  366. * "view" of part of the tree, not an actual node/entity in the tree) that
  367. * delimits a doubly-linked list of sibling nodes.
  368. * It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
  369. * classes: like DocumentFragment, its contents must be sibling nodes
  370. * (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
  371. * but like Range, it has only one-way pointers to its contents, its contents
  372. * have no reference to it and in fact may still be in the visible tree (unlike
  373. * DocumentFragment, whose contents must be detached from the visible tree
  374. * and have their 'parent' pointers set to the DocumentFragment).
  375. */
  376. var Fragment = P(function(_) {
  377. _.init = function(withDir, oppDir, dir) {
  378. if (dir === undefined) dir = L;
  379. prayDirection(dir);
  380. pray('no half-empty fragments', !withDir === !oppDir);
  381. this.ends = {};
  382. if (!withDir) return;
  383. pray('withDir is passed to Fragment', withDir instanceof Node);
  384. pray('oppDir is passed to Fragment', oppDir instanceof Node);
  385. pray('withDir and oppDir have the same parent',
  386. withDir.parent === oppDir.parent);
  387. this.ends[dir] = withDir;
  388. this.ends[-dir] = oppDir;
  389. // To build the jquery collection for a fragment, accumulate elements
  390. // into an array and then call jQ.add once on the result. jQ.add sorts the
  391. // collection according to document order each time it is called, so
  392. // building a collection by folding jQ.add directly takes more than
  393. // quadratic time in the number of elements.
  394. //
  395. // https://github.com/jquery/jquery/blob/2.1.4/src/traversing.js#L112
  396. var accum = this.fold([], function (accum, el) {
  397. accum.push.apply(accum, el.jQ.get());
  398. return accum;
  399. });
  400. this.jQ = this.jQ.add(accum);
  401. };
  402. _.jQ = $();
  403. // like Cursor::withDirInsertAt(dir, parent, withDir, oppDir)
  404. _.withDirAdopt = function(dir, parent, withDir, oppDir) {
  405. return (dir === L ? this.adopt(parent, withDir, oppDir)
  406. : this.adopt(parent, oppDir, withDir));
  407. };
  408. _.adopt = function(parent, leftward, rightward) {
  409. prayWellFormed(parent, leftward, rightward);
  410. var self = this;
  411. self.disowned = false;
  412. var leftEnd = self.ends[L];
  413. if (!leftEnd) return this;
  414. var rightEnd = self.ends[R];
  415. if (leftward) {
  416. // NB: this is handled in the ::each() block
  417. // leftward[R] = leftEnd
  418. } else {
  419. parent.ends[L] = leftEnd;
  420. }
  421. if (rightward) {
  422. rightward[L] = rightEnd;
  423. } else {
  424. parent.ends[R] = rightEnd;
  425. }
  426. self.ends[R][R] = rightward;
  427. self.each(function(el) {
  428. el[L] = leftward;
  429. el.parent = parent;
  430. if (leftward) leftward[R] = el;
  431. leftward = el;
  432. });
  433. return self;
  434. };
  435. _.disown = function() {
  436. var self = this;
  437. var leftEnd = self.ends[L];
  438. // guard for empty and already-disowned fragments
  439. if (!leftEnd || self.disowned) return self;
  440. self.disowned = true;
  441. var rightEnd = self.ends[R]
  442. var parent = leftEnd.parent;
  443. prayWellFormed(parent, leftEnd[L], leftEnd);
  444. prayWellFormed(parent, rightEnd, rightEnd[R]);
  445. if (leftEnd[L]) {
  446. leftEnd[L][R] = rightEnd[R];
  447. } else {
  448. parent.ends[L] = rightEnd[R];
  449. }
  450. if (rightEnd[R]) {
  451. rightEnd[R][L] = leftEnd[L];
  452. } else {
  453. parent.ends[R] = leftEnd[L];
  454. }
  455. return self;
  456. };
  457. _.remove = function() {
  458. this.jQ.remove();
  459. this.each('postOrder', 'dispose');
  460. return this.disown();
  461. };
  462. _.each = iterator(function(yield_) {
  463. var self = this;
  464. var el = self.ends[L];
  465. if (!el) return self;
  466. for (; el !== self.ends[R][R]; el = el[R]) {
  467. var result = yield_(el);
  468. if (result === false) break;
  469. }
  470. return self;
  471. });
  472. _.fold = function(fold, fn) {
  473. this.each(function(el) {
  474. fold = fn.call(this, fold, el);
  475. });
  476. return fold;
  477. };
  478. });
  479. /**
  480. * Registry of LaTeX commands and commands created when typing
  481. * a single character.
  482. *
  483. * (Commands are all subclasses of Node.)
  484. */
  485. var LatexCmds = {}, CharCmds = {};
  486. /********************************************
  487. * Cursor and Selection "singleton" classes
  488. *******************************************/
  489. /* The main thing that manipulates the Math DOM. Makes sure to manipulate the
  490. HTML DOM to match. */
  491. /* Sort of singletons, since there should only be one per editable math
  492. textbox, but any one HTML document can contain many such textboxes, so any one
  493. JS environment could actually contain many instances. */
  494. //A fake cursor in the fake textbox that the math is rendered in.
  495. var Cursor = P(Point, function(_) {
  496. _.init = function(initParent, options) {
  497. this.parent = initParent;
  498. this.options = options;
  499. var jQ = this.jQ = this._jQ = $('<span class="mq-cursor">&#8203;</span>');
  500. //closured for setInterval
  501. this.blink = function(){ jQ.toggleClass('mq-blink'); };
  502. this.upDownCache = {};
  503. };
  504. _.show = function() {
  505. this.jQ = this._jQ.removeClass('mq-blink');
  506. if ('intervalId' in this) //already was shown, just restart interval
  507. clearInterval(this.intervalId);
  508. else { //was hidden and detached, insert this.jQ back into HTML DOM
  509. if (this[R]) {
  510. if (this.selection && this.selection.ends[L][L] === this[L])
  511. this.jQ.insertBefore(this.selection.jQ);
  512. else
  513. this.jQ.insertBefore(this[R].jQ.first());
  514. }
  515. else
  516. this.jQ.appendTo(this.parent.jQ);
  517. this.parent.focus();
  518. }
  519. this.intervalId = setInterval(this.blink, 500);
  520. return this;
  521. };
  522. _.hide = function() {
  523. if ('intervalId' in this)
  524. clearInterval(this.intervalId);
  525. delete this.intervalId;
  526. this.jQ.detach();
  527. this.jQ = $();
  528. return this;
  529. };
  530. _.withDirInsertAt = function(dir, parent, withDir, oppDir) {
  531. var oldParent = this.parent;
  532. this.parent = parent;
  533. this[dir] = withDir;
  534. this[-dir] = oppDir;
  535. // by contract, .blur() is called after all has been said and done
  536. // and the cursor has actually been moved
  537. if (oldParent !== parent && oldParent.blur) oldParent.blur();
  538. };
  539. _.insDirOf = function(dir, el) {
  540. prayDirection(dir);
  541. this.jQ.insDirOf(dir, el.jQ);
  542. this.withDirInsertAt(dir, el.parent, el[dir], el);
  543. this.parent.jQ.addClass('mq-hasCursor');
  544. return this;
  545. };
  546. _.insLeftOf = function(el) { return this.insDirOf(L, el); };
  547. _.insRightOf = function(el) { return this.insDirOf(R, el); };
  548. _.insAtDirEnd = function(dir, el) {
  549. prayDirection(dir);
  550. this.jQ.insAtDirEnd(dir, el.jQ);
  551. this.withDirInsertAt(dir, el, 0, el.ends[dir]);
  552. el.focus();
  553. return this;
  554. };
  555. _.insAtLeftEnd = function(el) { return this.insAtDirEnd(L, el); };
  556. _.insAtRightEnd = function(el) { return this.insAtDirEnd(R, el); };
  557. /**
  558. * jump up or down from one block Node to another:
  559. * - cache the current Point in the node we're jumping from
  560. * - check if there's a Point in it cached for the node we're jumping to
  561. * + if so put the cursor there,
  562. * + if not seek a position in the node that is horizontally closest to
  563. * the cursor's current position
  564. */
  565. _.jumpUpDown = function(from, to) {
  566. var self = this;
  567. self.upDownCache[from.id] = Point.copy(self);
  568. var cached = self.upDownCache[to.id];
  569. if (cached) {
  570. cached[R] ? self.insLeftOf(cached[R]) : self.insAtRightEnd(cached.parent);
  571. }
  572. else {
  573. var pageX = self.offset().left;
  574. to.seek(pageX, self);
  575. }
  576. };
  577. _.offset = function() {
  578. //in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset()
  579. //returns all 0's on inline elements with negative margin-right (like
  580. //the cursor) at the end of their parent, so temporarily remove the
  581. //negative margin-right when calling jQuery::offset()
  582. //Opera bug DSK-360043
  583. //http://bugs.jquery.com/ticket/11523
  584. //https://github.com/jquery/jquery/pull/717
  585. var self = this, offset = self.jQ.removeClass('mq-cursor').offset();
  586. self.jQ.addClass('mq-cursor');
  587. return offset;
  588. }
  589. _.unwrapGramp = function() {
  590. var gramp = this.parent.parent;
  591. var greatgramp = gramp.parent;
  592. var rightward = gramp[R];
  593. var cursor = this;
  594. var leftward = gramp[L];
  595. gramp.disown().eachChild(function(uncle) {
  596. if (uncle.isEmpty()) return;
  597. uncle.children()
  598. .adopt(greatgramp, leftward, rightward)
  599. .each(function(cousin) {
  600. cousin.jQ.insertBefore(gramp.jQ.first());
  601. })
  602. ;
  603. leftward = uncle.ends[R];
  604. });
  605. if (!this[R]) { //then find something to be rightward to insLeftOf
  606. if (this[L])
  607. this[R] = this[L][R];
  608. else {
  609. while (!this[R]) {
  610. this.parent = this.parent[R];
  611. if (this.parent)
  612. this[R] = this.parent.ends[L];
  613. else {
  614. this[R] = gramp[R];
  615. this.parent = greatgramp;
  616. break;
  617. }
  618. }
  619. }
  620. }
  621. if (this[R])
  622. this.insLeftOf(this[R]);
  623. else
  624. this.insAtRightEnd(greatgramp);
  625. gramp.jQ.remove();
  626. if (gramp[L].siblingDeleted) gramp[L].siblingDeleted(cursor.options, R);
  627. if (gramp[R].siblingDeleted) gramp[R].siblingDeleted(cursor.options, L);
  628. };
  629. _.startSelection = function() {
  630. var anticursor = this.anticursor = Point.copy(this);
  631. var ancestors = anticursor.ancestors = {}; // a map from each ancestor of
  632. // the anticursor, to its child that is also an ancestor; in other words,
  633. // the anticursor's ancestor chain in reverse order
  634. for (var ancestor = anticursor; ancestor.parent; ancestor = ancestor.parent) {
  635. ancestors[ancestor.parent.id] = ancestor;
  636. }
  637. };
  638. _.endSelection = function() {
  639. delete this.anticursor;
  640. };
  641. _.select = function() {
  642. var anticursor = this.anticursor;
  643. if (this[L] === anticursor[L] && this.parent === anticursor.parent) return false;
  644. // Find the lowest common ancestor (`lca`), and the ancestor of the cursor
  645. // whose parent is the LCA (which'll be an end of the selection fragment).
  646. for (var ancestor = this; ancestor.parent; ancestor = ancestor.parent) {
  647. if (ancestor.parent.id in anticursor.ancestors) {
  648. var lca = ancestor.parent;
  649. break;
  650. }
  651. }
  652. pray('cursor and anticursor in the same tree', lca);
  653. // The cursor and the anticursor should be in the same tree, because the
  654. // mousemove handler attached to the document, unlike the one attached to
  655. // the root HTML DOM element, doesn't try to get the math tree node of the
  656. // mousemove target, and Cursor::seek() based solely on coordinates stays
  657. // within the tree of `this` cursor's root.
  658. // The other end of the selection fragment, the ancestor of the anticursor
  659. // whose parent is the LCA.
  660. var antiAncestor = anticursor.ancestors[lca.id];
  661. // Now we have two either Nodes or Points, guaranteed to have a common
  662. // parent and guaranteed that if both are Points, they are not the same,
  663. // and we have to figure out which is the left end and which the right end
  664. // of the selection.
  665. var leftEnd, rightEnd, dir = R;
  666. // This is an extremely subtle algorithm.
  667. // As a special case, `ancestor` could be a Point and `antiAncestor` a Node
  668. // immediately to `ancestor`'s left.
  669. // In all other cases,
  670. // - both Nodes
  671. // - `ancestor` a Point and `antiAncestor` a Node
  672. // - `ancestor` a Node and `antiAncestor` a Point
  673. // `antiAncestor[R] === rightward[R]` for some `rightward` that is
  674. // `ancestor` or to its right, if and only if `antiAncestor` is to
  675. // the right of `ancestor`.
  676. if (ancestor[L] !== antiAncestor) {
  677. for (var rightward = ancestor; rightward; rightward = rightward[R]) {
  678. if (rightward[R] === antiAncestor[R]) {
  679. dir = L;
  680. leftEnd = ancestor;
  681. rightEnd = antiAncestor;
  682. break;
  683. }
  684. }
  685. }
  686. if (dir === R) {
  687. leftEnd = antiAncestor;
  688. rightEnd = ancestor;
  689. }
  690. // only want to select Nodes up to Points, can't select Points themselves
  691. if (leftEnd instanceof Point) leftEnd = leftEnd[R];
  692. if (rightEnd instanceof Point) rightEnd = rightEnd[L];
  693. this.hide().selection = lca.selectChildren(leftEnd, rightEnd);
  694. this.insDirOf(dir, this.selection.ends[dir]);
  695. this.selectionChanged();
  696. return true;
  697. };
  698. _.clearSelection = function() {
  699. if (this.selection) {
  700. this.selection.clear();
  701. delete this.selection;
  702. this.selectionChanged();
  703. }
  704. return this;
  705. };
  706. _.deleteSelection = function() {
  707. if (!this.selection) return;
  708. this[L] = this.selection.ends[L][L];
  709. this[R] = this.selection.ends[R][R];
  710. this.selection.remove();
  711. this.selectionChanged();
  712. delete this.selection;
  713. };
  714. _.replaceSelection = function() {
  715. var seln = this.selection;
  716. if (seln) {
  717. this[L] = seln.ends[L][L];
  718. this[R] = seln.ends[R][R];
  719. delete this.selection;
  720. }
  721. return seln;
  722. };
  723. });
  724. var Selection = P(Fragment, function(_, super_) {
  725. _.init = function() {
  726. super_.init.apply(this, arguments);
  727. this.jQ = this.jQ.wrapAll('<span class="mq-selection"></span>').parent();
  728. //can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it
  729. };
  730. _.adopt = function() {
  731. this.jQ.replaceWith(this.jQ = this.jQ.children());
  732. return super_.adopt.apply(this, arguments);
  733. };
  734. _.clear = function() {
  735. // using the browser's native .childNodes property so that we
  736. // don't discard text nodes.
  737. this.jQ.replaceWith(this.jQ[0].childNodes);
  738. return this;
  739. };
  740. _.join = function(methodName) {
  741. return this.fold('', function(fold, child) {
  742. return fold + child[methodName]();
  743. });
  744. };
  745. });
  746. /*********************************************
  747. * Controller for a MathQuill instance,
  748. * on which services are registered with
  749. *
  750. * Controller.open(function(_) { ... });
  751. *
  752. ********************************************/
  753. var Controller = P(function(_) {
  754. _.init = function(root, container, options) {
  755. this.id = root.id;
  756. this.data = {};
  757. this.root = root;
  758. this.container = container;
  759. this.options = options;
  760. root.controller = this;
  761. this.cursor = root.cursor = Cursor(root, options);
  762. // TODO: stop depending on root.cursor, and rm it
  763. };
  764. _.handle = function(name, dir) {
  765. var handlers = this.options.handlers;
  766. if (handlers && handlers.fns[name]) {
  767. var mq = handlers.APIClasses[this.KIND_OF_MQ](this);
  768. if (dir === L || dir === R) handlers.fns[name](dir, mq);
  769. else handlers.fns[name](mq);
  770. }
  771. };
  772. var notifyees = [];
  773. this.onNotify = function(f) { notifyees.push(f); };
  774. _.notify = function() {
  775. for (var i = 0; i < notifyees.length; i += 1) {
  776. notifyees[i].apply(this.cursor, arguments);
  777. }
  778. return this;
  779. };
  780. });
  781. /*********************************************************
  782. * The publicly exposed MathQuill API.
  783. ********************************************************/
  784. var API = {}, Options = P(), optionProcessors = {}, Progenote = P(), EMBEDS = {};
  785. /**
  786. * Interface Versioning (#459, #495) to allow us to virtually guarantee
  787. * backcompat. v0.10.x introduces it, so for now, don't completely break the
  788. * API for people who don't know about it, just complain with console.warn().
  789. *
  790. * The methods are shimmed in outro.js so that MQ.MathField.prototype etc can
  791. * be accessed.
  792. */
  793. function insistOnInterVer() {
  794. if (window.console) console.warn(
  795. 'You are using the MathQuill API without specifying an interface version, ' +
  796. 'which will fail in v1.0.0. You can fix this easily by doing this before ' +
  797. 'doing anything else:\n' +
  798. '\n' +
  799. ' MathQuill = MathQuill.getInterface(1);\n' +
  800. ' // now MathQuill.MathField() works like it used to\n' +
  801. '\n' +
  802. 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' +
  803. ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
  804. );
  805. }
  806. // globally exported API object
  807. function MathQuill(el) {
  808. insistOnInterVer();
  809. return MQ1(el);
  810. };
  811. MathQuill.prototype = Progenote.p;
  812. MathQuill.interfaceVersion = function(v) {
  813. // shim for #459-era interface versioning (ended with #495)
  814. if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v;
  815. insistOnInterVer = function() {
  816. if (window.console) console.warn(
  817. 'You called MathQuill.interfaceVersion(1); to specify the interface ' +
  818. 'version, which will fail in v1.0.0. You can fix this easily by doing ' +
  819. 'this before doing anything else:\n' +
  820. '\n' +
  821. ' MathQuill = MathQuill.getInterface(1);\n' +
  822. ' // now MathQuill.MathField() works like it used to\n' +
  823. '\n' +
  824. 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' +
  825. ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
  826. );
  827. };
  828. insistOnInterVer();
  829. return MathQuill;
  830. };
  831. MathQuill.getInterface = getInterface;
  832. var MIN = getInterface.MIN = 1, MAX = getInterface.MAX = 2;
  833. function getInterface(v) {
  834. if (!(MIN <= v && v <= MAX)) throw 'Only interface versions between ' +
  835. MIN + ' and ' + MAX + ' supported. You specified: ' + v;
  836. /**
  837. * Function that takes an HTML element and, if it's the root HTML element of a
  838. * static math or math or text field, returns an API object for it (else, null).
  839. *
  840. * var mathfield = MQ.MathField(mathFieldSpan);
  841. * assert(MQ(mathFieldSpan).id === mathfield.id);
  842. * assert(MQ(mathFieldSpan).id === MQ(mathFieldSpan).id);
  843. *
  844. */
  845. function MQ(el) {
  846. if (!el || !el.nodeType) return null; // check that `el` is a HTML element, using the
  847. // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92
  848. var blockId = $(el).children('.mq-root-block').attr(mqBlockId);
  849. var ctrlr = blockId && Node.byId[blockId].controller;
  850. return ctrlr ? APIClasses[ctrlr.KIND_OF_MQ](ctrlr) : null;
  851. };
  852. var APIClasses = {};
  853. MQ.L = L;
  854. MQ.R = R;
  855. function config(currentOptions, newOptions) {
  856. if (newOptions && newOptions.handlers) {
  857. newOptions.handlers = { fns: newOptions.handlers, APIClasses: APIClasses };
  858. }
  859. for (var name in newOptions) if (newOptions.hasOwnProperty(name)) {
  860. var value = newOptions[name], processor = optionProcessors[name];
  861. currentOptions[name] = (processor ? processor(value) : value);
  862. }
  863. }
  864. MQ.config = function(opts) { config(Options.p, opts); return this; };
  865. MQ.registerEmbed = function(name, options) {
  866. if (!/^[a-z][a-z0-9]*$/i.test(name)) {
  867. throw 'Embed name must start with letter and be only letters and digits';
  868. }
  869. EMBEDS[name] = options;
  870. };
  871. var AbstractMathQuill = APIClasses.AbstractMathQuill = P(Progenote, function(_) {
  872. _.init = function(ctrlr) {
  873. this.__controller = ctrlr;
  874. this.__options = ctrlr.options;
  875. this.id = ctrlr.id;
  876. this.data = ctrlr.data;
  877. };
  878. _.__mathquillify = function(classNames) {
  879. var ctrlr = this.__controller, root = ctrlr.root, el = ctrlr.container;
  880. ctrlr.createTextarea();
  881. var contents = el.addClass(classNames).contents().detach();
  882. root.jQ =
  883. $('<span class="mq-root-block"/>').attr(mqBlockId, root.id).appendTo(el);
  884. this.latex(contents.text());
  885. this.revert = function() {
  886. return el.empty().unbind('.mathquill')
  887. .removeClass('mq-editable-field mq-math-mode mq-text-mode')
  888. .append(contents);
  889. };
  890. };
  891. _.config = function(opts) { config(this.__options, opts); return this; };
  892. _.el = function() { return this.__controller.container[0]; };
  893. _.text = function() { return this.__controller.exportText(); };
  894. _.latex = function(latex) {
  895. if (arguments.length > 0) {
  896. this.__controller.renderLatexMath(latex);
  897. if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
  898. return this;
  899. }
  900. return this.__controller.exportLatex();
  901. };
  902. _.html = function() {
  903. return this.__controller.root.jQ.html()
  904. .replace(/ mathquill-(?:command|block)-id="?\d+"?/g, '')
  905. .replace(/<span class="?mq-cursor( mq-blink)?"?>.?<\/span>/i, '')
  906. .replace(/ mq-hasCursor|mq-hasCursor ?/, '')
  907. .replace(/ class=(""|(?= |>))/g, '');
  908. };
  909. _.reflow = function() {
  910. this.__controller.root.postOrder('reflow');
  911. return this;
  912. };
  913. });
  914. MQ.prototype = AbstractMathQuill.prototype;
  915. APIClasses.EditableField = P(AbstractMathQuill, function(_, super_) {
  916. _.__mathquillify = function() {
  917. super_.__mathquillify.apply(this, arguments);
  918. this.__controller.editable = true;
  919. this.__controller.delegateMouseEvents();
  920. this.__controller.editablesTextareaEvents();
  921. return this;
  922. };
  923. _.focus = function() { this.__controller.textarea.focus(); return this; };
  924. _.blur = function() { this.__controller.textarea.blur(); return this; };
  925. _.write = function(latex) {
  926. this.__controller.writeLatex(latex);
  927. this.__controller.scrollHoriz();
  928. if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
  929. return this;
  930. };
  931. _.cmd = function(cmd) {
  932. var ctrlr = this.__controller.notify(), cursor = ctrlr.cursor;
  933. if (/^\\[a-z]+$/i.test(cmd)) {
  934. cmd = cmd.slice(1);
  935. var klass = LatexCmds[cmd];
  936. if (klass) {
  937. cmd = klass(cmd);
  938. if (cursor.selection) cmd.replaces(cursor.replaceSelection());
  939. cmd.createLeftOf(cursor.show());
  940. this.__controller.scrollHoriz();
  941. }
  942. else /* TODO: API needs better error reporting */;
  943. }
  944. else cursor.parent.write(cursor, cmd);
  945. if (ctrlr.blurred) cursor.hide().parent.blur();
  946. return this;
  947. };
  948. _.select = function() {
  949. var ctrlr = this.__controller;
  950. ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
  951. while (ctrlr.cursor[L]) ctrlr.selectLeft();
  952. return this;
  953. };
  954. _.clearSelection = function() {
  955. this.__controller.cursor.clearSelection();
  956. return this;
  957. };
  958. _.moveToDirEnd = function(dir) {
  959. this.__controller.notify('move').cursor.insAtDirEnd(dir, this.__controller.root);
  960. return this;
  961. };
  962. _.moveToLeftEnd = function() { return this.moveToDirEnd(L); };
  963. _.moveToRightEnd = function() { return this.moveToDirEnd(R); };
  964. _.keystroke = function(keys) {
  965. var keys = keys.replace(/^\s+|\s+$/g, '').split(/\s+/);
  966. for (var i = 0; i < keys.length; i += 1) {
  967. this.__controller.keystroke(keys[i], { preventDefault: noop });
  968. }
  969. return this;
  970. };
  971. _.typedText = function(text) {
  972. for (var i = 0; i < text.length; i += 1) this.__controller.typedText(text.charAt(i));
  973. return this;
  974. };
  975. _.dropEmbedded = function(pageX, pageY, options) {
  976. var clientX = pageX - $(window).scrollLeft();
  977. var clientY = pageY - $(window).scrollTop();
  978. var el = document.elementFromPoint(clientX, clientY);
  979. this.__controller.seek($(el), pageX, pageY);
  980. var cmd = Embed().setOptions(options);
  981. cmd.createLeftOf(this.__controller.cursor);
  982. };
  983. });
  984. MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; };
  985. MQ.EditableField.prototype = APIClasses.EditableField.prototype;
  986. /**
  987. * Export the API functions that MathQuill-ify an HTML element into API objects
  988. * of each class. If the element had already been MathQuill-ified but into a
  989. * different kind (or it's not an HTML element), return null.
  990. */
  991. for (var kind in API) (function(kind, defAPIClass) {
  992. var APIClass = APIClasses[kind] = defAPIClass(APIClasses);
  993. MQ[kind] = function(el, opts) {
  994. var mq = MQ(el);
  995. if (mq instanceof APIClass || !el || !el.nodeType) return mq;
  996. var ctrlr = Controller(APIClass.RootBlock(), $(el), Options());
  997. ctrlr.KIND_OF_MQ = kind;
  998. return APIClass(ctrlr).__mathquillify(opts, v);
  999. };
  1000. MQ[kind].prototype = APIClass.prototype;
  1001. }(kind, API[kind]));
  1002. return MQ;
  1003. }
  1004. MathQuill.noConflict = function() {
  1005. window.MathQuill = origMathQuill;
  1006. return MathQuill;
  1007. };
  1008. var origMathQuill = window.MathQuill;
  1009. window.MathQuill = MathQuill;
  1010. function RootBlockMixin(_) {
  1011. var names = 'moveOutOf deleteOutOf selectOutOf upOutOf downOutOf'.split(' ');
  1012. for (var i = 0; i < names.length; i += 1) (function(name) {
  1013. _[name] = function(dir) { this.controller.handle(name, dir); };
  1014. }(names[i]));
  1015. _.reflow = function() {
  1016. this.controller.handle('reflow');
  1017. this.controller.handle('edited');
  1018. this.controller.handle('edit');
  1019. };
  1020. }
  1021. var Parser = P(function(_, super_, Parser) {
  1022. // The Parser object is a wrapper for a parser function.
  1023. // Externally, you use one to parse a string by calling
  1024. // var result = SomeParser.parse('Me Me Me! Parse Me!');
  1025. // You should never call the constructor, rather you should
  1026. // construct your Parser from the base parsers and the
  1027. // parser combinator methods.
  1028. function parseError(stream, message) {
  1029. if (stream) {
  1030. stream = "'"+stream+"'";
  1031. }
  1032. else {
  1033. stream = 'EOF';
  1034. }
  1035. throw 'Parse Error: '+message+' at '+stream;
  1036. }
  1037. _.init = function(body) { this._ = body; };
  1038. _.parse = function(stream) {
  1039. return this.skip(eof)._(''+stream, success, parseError);
  1040. function success(stream, result) { return result; }
  1041. };
  1042. // -*- primitive combinators -*- //
  1043. _.or = function(alternative) {
  1044. pray('or is passed a parser', alternative instanceof Parser);
  1045. var self = this;
  1046. return Parser(function(stream, onSuccess, onFailure) {
  1047. return self._(stream, onSuccess, failure);
  1048. function failure(newStream) {
  1049. return alternative._(stream, onSuccess, onFailure);
  1050. }
  1051. });
  1052. };
  1053. _.then = function(next) {
  1054. var self = this;
  1055. return Parser(function(stream, onSuccess, onFailure) {
  1056. return self._(stream, success, onFailure);
  1057. function success(newStream, result) {
  1058. var nextParser = (next instanceof Parser ? next : next(result));
  1059. pray('a parser is returned', nextParser instanceof Parser);
  1060. return nextParser._(newStream, onSuccess, onFailure);
  1061. }
  1062. });
  1063. };
  1064. // -*- optimized iterative combinators -*- //
  1065. _.many = function() {
  1066. var self = this;
  1067. return Parser(function(stream, onSuccess, onFailure) {
  1068. var xs = [];
  1069. while (self._(stream, success, failure));
  1070. return onSuccess(stream, xs);
  1071. function success(newStream, x) {
  1072. stream = newStream;
  1073. xs.push(x);
  1074. return true;
  1075. }
  1076. function failure() {
  1077. return false;
  1078. }
  1079. });
  1080. };
  1081. _.times = function(min, max) {
  1082. if (arguments.length < 2) max = min;
  1083. var self = this;
  1084. return Parser(function(stream, onSuccess, onFailure) {
  1085. var xs = [];
  1086. var result = true;
  1087. var failure;
  1088. for (var i = 0; i < min; i += 1) {
  1089. result = self._(stream, success, firstFailure);
  1090. if (!result) return onFailure(stream, failure);
  1091. }
  1092. for (; i < max && result; i += 1) {
  1093. result = self._(stream, success, secondFailure);
  1094. }
  1095. return onSuccess(stream, xs);
  1096. function success(newStream, x) {
  1097. xs.push(x);
  1098. stream = newStream;
  1099. return true;
  1100. }
  1101. function firstFailure(newStream, msg) {
  1102. failure = msg;
  1103. stream = newStream;
  1104. return false;
  1105. }
  1106. function secondFailure(newStream, msg) {
  1107. return false;
  1108. }
  1109. });
  1110. };
  1111. // -*- higher-level combinators -*- //
  1112. _.result = function(res) { return this.then(succeed(res)); };
  1113. _.atMost = function(n) { return this.times(0, n); };
  1114. _.atLeast = function(n) {
  1115. var self = this;
  1116. return self.times(n).then(function(start) {
  1117. return self.many().map(function(end) {
  1118. return start.concat(end);
  1119. });
  1120. });
  1121. };
  1122. _.map = function(fn) {
  1123. return this.then(function(result) { return succeed(fn(result)); });
  1124. };
  1125. _.skip = function(two) {
  1126. return this.then(function(result) { return two.result(result); });
  1127. };
  1128. // -*- primitive parsers -*- //
  1129. var string = this.string = function(str) {
  1130. var len = str.length;
  1131. var expected = "expected '"+str+"'";
  1132. return Parser(function(stream, onSuccess, onFailure) {
  1133. var head = stream.slice(0, len);
  1134. if (head === str) {
  1135. return onSuccess(stream.slice(len), head);
  1136. }
  1137. else {
  1138. return onFailure(stream, expected);
  1139. }
  1140. });
  1141. };
  1142. var regex = this.regex = function(re) {
  1143. pray('regexp parser is anchored', re.toString().charAt(1) === '^');
  1144. var expected = 'expected '+re;
  1145. return Parser(function(stream, onSuccess, onFailure) {
  1146. var match = re.exec(stream);
  1147. if (match) {
  1148. var result = match[0];
  1149. return onSuccess(stream.slice(result.length), result);
  1150. }
  1151. else {
  1152. return onFailure(stream, expected);
  1153. }
  1154. });
  1155. };
  1156. var succeed = Parser.succeed = function(result) {
  1157. return Parser(function(stream, onSuccess) {
  1158. return onSuccess(stream, result);
  1159. });
  1160. };
  1161. var fail = Parser.fail = function(msg) {
  1162. return Parser(function(stream, _, onFailure) {
  1163. return onFailure(stream, msg);
  1164. });
  1165. };
  1166. var letter = Parser.letter = regex(/^[a-z]/i);
  1167. var letters = Parser.letters = regex(/^[a-z]*/i);
  1168. var digit = Parser.digit = regex(/^[0-9]/);
  1169. var digits = Parser.digits = regex(/^[0-9]*/);
  1170. var whitespace = Parser.whitespace = regex(/^\s+/);
  1171. var optWhitespace = Parser.optWhitespace = regex(/^\s*/);
  1172. var any = Parser.any = Parser(function(stream, onSuccess, onFailure) {
  1173. if (!stream) return onFailure(stream, 'expected any character');
  1174. return onSuccess(stream.slice(1), stream.charAt(0));
  1175. });
  1176. var all = Parser.all = Parser(function(stream, onSuccess, onFailure) {
  1177. return onSuccess('', stream);
  1178. });
  1179. var eof = Parser.eof = Parser(function(stream, onSuccess, onFailure) {
  1180. if (stream) return onFailure(stream, 'expected EOF');
  1181. return onSuccess(stream, stream);
  1182. });
  1183. });
  1184. /*************************************************
  1185. * Sane Keyboard Events Shim
  1186. *
  1187. * An abstraction layer wrapping the textarea in
  1188. * an object with methods to manipulate and listen
  1189. * to events on, that hides all the nasty cross-
  1190. * browser incompatibilities behind a uniform API.
  1191. *
  1192. * Design goal: This is a *HARD* internal
  1193. * abstraction barrier. Cross-browser
  1194. * inconsistencies are not allowed to leak through
  1195. * and be dealt with by event handlers. All future
  1196. * cross-browser issues that arise must be dealt
  1197. * with here, and if necessary, the API updated.
  1198. *
  1199. * Organization:
  1200. * - key values map and stringify()
  1201. * - saneKeyboardEvents()
  1202. * + defer() and flush()
  1203. * + event handler logic
  1204. * + attach event handlers and export methods
  1205. ************************************************/
  1206. var saneKeyboardEvents = (function() {
  1207. // The following [key values][1] map was compiled from the
  1208. // [DOM3 Events appendix section on key codes][2] and
  1209. // [a widely cited report on cross-browser tests of key codes][3],
  1210. // except for 10: 'Enter', which I've empirically observed in Safari on iOS
  1211. // and doesn't appear to conflict with any other known key codes.
  1212. //
  1213. // [1]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#keys-keyvalues
  1214. // [2]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes
  1215. // [3]: http://unixpapa.com/js/key.html
  1216. var KEY_VALUES = {
  1217. 8: 'Backspace',
  1218. 9: 'Tab',
  1219. 10: 'Enter', // for Safari on iOS
  1220. 13: 'Enter',
  1221. 16: 'Shift',
  1222. 17: 'Control',
  1223. 18: 'Alt',
  1224. 20: 'CapsLock',
  1225. 27: 'Esc',
  1226. 32: 'Spacebar',
  1227. 33: 'PageUp',
  1228. 34: 'PageDown',
  1229. 35: 'End',
  1230. 36: 'Home',
  1231. 37: 'Left',
  1232. 38: 'Up',
  1233. 39: 'Right',
  1234. 40: 'Down',
  1235. 45: 'Insert',
  1236. 46: 'Del',
  1237. 144: 'NumLock'
  1238. };
  1239. // To the extent possible, create a normalized string representation
  1240. // of the key combo (i.e., key code and modifier keys).
  1241. function stringify(evt) {
  1242. var which = evt.which || evt.keyCode;
  1243. var keyVal = KEY_VALUES[which];
  1244. var key;
  1245. var modifiers = [];
  1246. if (evt.ctrlKey) modifiers.push('Ctrl');
  1247. if (evt.originalEvent && evt.originalEvent.metaKey) modifiers.push('Meta');
  1248. if (evt.altKey) modifiers.push('Alt');
  1249. if (evt.shiftKey) modifiers.push('Shift');
  1250. key = keyVal || String.fromCharCode(which);
  1251. if (!modifiers.length && !keyVal) return key;
  1252. modifiers.push(key);
  1253. return modifiers.join('-');
  1254. }
  1255. // create a keyboard events shim that calls callbacks at useful times
  1256. // and exports useful public methods
  1257. return function saneKeyboardEvents(el, handlers) {
  1258. var keydown = null;
  1259. var keypress = null;
  1260. var textarea = jQuery(el);
  1261. var target = jQuery(handlers.container || textarea);
  1262. // checkTextareaFor() is called after keypress or paste events to
  1263. // say "Hey, I think something was just typed" or "pasted" (resp.),
  1264. // so that at all subsequent opportune times (next event or timeout),
  1265. // will check for expected typed or pasted text.
  1266. // Need to check repeatedly because #135: in Safari 5.1 (at least),
  1267. // after selecting something and then typing, the textarea is
  1268. // incorrectly reported as selected during the input event (but not
  1269. // subsequently).
  1270. var checkTextarea = noop, timeoutId;
  1271. function checkTextareaFor(checker) {
  1272. checkTextarea = checker;
  1273. clearTimeout(timeoutId);
  1274. timeoutId = setTimeout(checker);
  1275. }
  1276. target.bind('keydown keypress input keyup focusout paste', function(e) { checkTextarea(e); });
  1277. // -*- public methods -*- //
  1278. function select(text) {
  1279. // check textarea at least once/one last time before munging (so
  1280. // no race condition if selection happens after keypress/paste but
  1281. // before checkTextarea), then never again ('cos it's been munged)
  1282. checkTextarea();
  1283. checkTextarea = noop;
  1284. clearTimeout(timeoutId);
  1285. textarea.val(text);
  1286. if (text && textarea[0].select) textarea[0].select();
  1287. shouldBeSelected = !!text;
  1288. }
  1289. var shouldBeSelected = false;
  1290. // -*- helper subroutines -*- //
  1291. // Determine whether there's a selection in the textarea.
  1292. // This will always return false in IE < 9, which don't support
  1293. // HTMLTextareaElement::selection{Start,End}.
  1294. function hasSelection() {
  1295. var dom = textarea[0];
  1296. if (!('selectionStart' in dom)) return false;
  1297. return dom.selectionStart !== dom.selectionEnd;
  1298. }
  1299. function handleKey() {
  1300. handlers.keystroke(stringify(keydown), keydown);
  1301. }
  1302. // -*- event handlers -*- //
  1303. function onKeydown(e) {
  1304. keydown = e;
  1305. keypress = null;
  1306. if (shouldBeSelected) checkTextareaFor(function(e) {
  1307. if (!(e && e.type === 'focusout') && textarea[0].select) {
  1308. textarea[0].select(); // re-select textarea in case it's an unrecognized
  1309. }
  1310. checkTextarea = noop; // key that clears the selection, then never
  1311. clearTimeout(timeoutId); // again, 'cos next thing might be blur
  1312. });
  1313. handleKey();
  1314. }
  1315. function onKeypress(e) {
  1316. // call the key handler for repeated keypresses.
  1317. // This excludes keypresses that happen directly
  1318. // after keydown. In that case, there will be
  1319. // no previous keypress, so we skip it here
  1320. if (keydown && keypress) handleKey();
  1321. keypress = e;
  1322. checkTextareaFor(typedText);
  1323. }
  1324. function typedText() {
  1325. // If there is a selection, the contents of the textarea couldn't
  1326. // possibly have just been typed in.
  1327. // This happens in browsers like Firefox and Opera that fire
  1328. // keypress for keystrokes that are not text entry and leave the
  1329. // selection in the textarea alone, such as Ctrl-C.
  1330. // Note: we assume that browsers that don't support hasSelection()
  1331. // also never fire keypress on keystrokes that are not text entry.
  1332. // This seems reasonably safe because:
  1333. // - all modern browsers including IE 9+ support hasSelection(),
  1334. // making it extremely unlikely any browser besides IE < 9 won't
  1335. // - as far as we know IE < 9 never fires keypress on keystrokes
  1336. // that aren't text entry, which is only as reliable as our
  1337. // tests are comprehensive, but the IE < 9 way to do
  1338. // hasSelection() is poorly documented and is also only as
  1339. // reliable as our tests are comprehensive
  1340. // If anything like #40 or #71 is reported in IE < 9, see
  1341. // b1318e5349160b665003e36d4eedd64101ceacd8
  1342. if (hasSelection()) return;
  1343. var text = textarea.val();
  1344. if (text.length === 1) {
  1345. textarea.val('');
  1346. handlers.typedText(text);
  1347. } // in Firefox, keys that don't type text, just clear seln, fire keypress
  1348. // https://github.com/mathquill/mathquill/issues/293#issuecomment-40997668
  1349. else if (text && textarea[0].select) textarea[0].select(); // re-select if that's why we're here
  1350. }
  1351. function onBlur() { keydown = keypress = null; }
  1352. function onPaste(e) {
  1353. // browsers are dumb.
  1354. //
  1355. // In Linux, middle-click pasting causes onPaste to be called,
  1356. // when the textarea is not necessarily focused. We focus it
  1357. // here to ensure that the pasted text actually ends up in the
  1358. // textarea.
  1359. //
  1360. // It's pretty nifty that by changing focus in this handler,
  1361. // we can change the target of the default action. (This works
  1362. // on keydown too, FWIW).
  1363. //
  1364. // And by nifty, we mean dumb (but useful sometimes).
  1365. textarea.focus();
  1366. checkTextareaFor(pastedText);
  1367. }
  1368. function pastedText() {
  1369. var text = textarea.val();
  1370. textarea.val('');
  1371. if (text) handlers.paste(text);
  1372. }
  1373. // -*- attach event handlers -*- //
  1374. target.bind({
  1375. keydown: onKeydown,
  1376. keypress: onKeypress,
  1377. focusout: onBlur,
  1378. paste: onPaste
  1379. });
  1380. // -*- export public methods -*- //
  1381. return {
  1382. select: select
  1383. };
  1384. };
  1385. }());
  1386. /***********************************************
  1387. * Export math in a human-readable text format
  1388. * As you can see, only half-baked so far.
  1389. **********************************************/
  1390. Controller.open(function(_, super_) {
  1391. _.exportText = function() {
  1392. return this.root.foldChildren('', function(text, child) {
  1393. return text + child.text();
  1394. });
  1395. };
  1396. });
  1397. Controller.open(function(_) {
  1398. _.focusBlurEvents = function() {
  1399. var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor;
  1400. var blurTimeout;
  1401. ctrlr.textarea.focus(function() {
  1402. ctrlr.blurred = false;
  1403. clearTimeout(blurTimeout);
  1404. ctrlr.container.addClass('mq-focused');
  1405. if (!cursor.parent)
  1406. cursor.insAtRightEnd(root);
  1407. if (cursor.selection) {
  1408. cursor.selection.jQ.removeClass('mq-blur');
  1409. ctrlr.selectionChanged(); //re-select textarea contents after tabbing away and back
  1410. }
  1411. else
  1412. cursor.show();
  1413. }).blur(function() {
  1414. ctrlr.blurred = true;
  1415. blurTimeout = setTimeout(function() { // wait for blur on window; if
  1416. root.postOrder('intentionalBlur'); // none, intentional blur: #264
  1417. cursor.clearSelection().endSelection();
  1418. blur();
  1419. });
  1420. $(window).on('blur', windowBlur);
  1421. });
  1422. function windowBlur() { // blur event also fired on window, just switching
  1423. clearTimeout(blurTimeout); // tabs/windows, not intentional blur
  1424. if (cursor.selection) cursor.selection.jQ.addClass('mq-blur');
  1425. blur();
  1426. }
  1427. function blur() { // not directly in the textarea blur handler so as to be
  1428. cursor.hide().parent.blur(); // synchronous with/in the same frame as
  1429. ctrlr.container.removeClass('mq-focused'); // clearing/blurring selection
  1430. $(window).off('blur', windowBlur);
  1431. }
  1432. ctrlr.blurred = true;
  1433. cursor.hide().parent.blur();
  1434. };
  1435. });
  1436. /**
  1437. * TODO: I wanted to move MathBlock::focus and blur here, it would clean
  1438. * up lots of stuff like, TextBlock::focus is set to MathBlock::focus
  1439. * and TextBlock::blur calls MathBlock::blur, when instead they could
  1440. * use inheritance and super_.
  1441. *
  1442. * Problem is, there's lots of calls to .focus()/.blur() on nodes
  1443. * outside Controller::focusBlurEvents(), such as .postOrder('blur') on
  1444. * insertion, which if MathBlock::blur becomes Node::blur, would add the
  1445. * 'blur' CSS class to all Symbol's (because .isEmpty() is true for all
  1446. * of them).
  1447. *
  1448. * I'm not even sure there aren't other troublesome calls to .focus() or
  1449. * .blur(), so this is TODO for now.
  1450. */
  1451. /*****************************************
  1452. * Deals with the browser DOM events from
  1453. * interaction with the typist.
  1454. ****************************************/
  1455. Controller.open(function(_) {
  1456. _.keystroke = function(key, evt) {
  1457. this.cursor.parent.keystroke(key, evt, this);
  1458. };
  1459. });
  1460. Node.open(function(_) {
  1461. _.keystroke = function(key, e, ctrlr) {
  1462. var cursor = ctrlr.cursor;
  1463. switch (key) {
  1464. case 'Ctrl-Shift-Backspace':
  1465. case 'Ctrl-Backspace':
  1466. ctrlr.ctrlDeleteDir(L);
  1467. break;
  1468. case 'Shift-Backspace':
  1469. case 'Backspace':
  1470. ctrlr.backspace();
  1471. break;
  1472. // Tab or Esc -> go one block right if it exists, else escape right.
  1473. case 'Esc':
  1474. case 'Tab':
  1475. ctrlr.escapeDir(R, key, e);
  1476. return;
  1477. // Shift-Tab -> go one block left if it exists, else escape left.
  1478. case 'Shift-Tab':
  1479. case 'Shift-Esc':
  1480. ctrlr.escapeDir(L, key, e);
  1481. return;
  1482. // End -> move to the end of the current block.
  1483. case 'End':
  1484. ctrlr.notify('move').cursor.insAtRightEnd(cursor.parent);
  1485. break;
  1486. // Ctrl-End -> move all the way to the end of the root block.
  1487. case 'Ctrl-End':
  1488. ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
  1489. break;
  1490. // Shift-End -> select to the end of the current block.
  1491. case 'Shift-End':
  1492. while (cursor[R]) {
  1493. ctrlr.selectRight();
  1494. }
  1495. break;
  1496. // Ctrl-Shift-End -> select to the end of the root block.
  1497. case 'Ctrl-Shift-End':
  1498. while (cursor[R] || cursor.parent !== ctrlr.root) {
  1499. ctrlr.selectRight();
  1500. }
  1501. break;
  1502. // Home -> move to the start of the root block or the current block.
  1503. case 'Home':
  1504. ctrlr.notify('move').cursor.insAtLeftEnd(cursor.parent);
  1505. break;
  1506. // Ctrl-Home -> move to the start of the current block.
  1507. case 'Ctrl-Home':
  1508. ctrlr.notify('move').cursor.insAtLeftEnd(ctrlr.root);
  1509. break;
  1510. // Shift-Home -> select to the start of the current block.
  1511. case 'Shift-Home':
  1512. while (cursor[L]) {
  1513. ctrlr.selectLeft();
  1514. }
  1515. break;
  1516. // Ctrl-Shift-Home -> move to the start of the root block.
  1517. case 'Ctrl-Shift-Home':
  1518. while (cursor[L] || cursor.parent !== ctrlr.root) {
  1519. ctrlr.selectLeft();
  1520. }
  1521. break;
  1522. case 'Left': ctrlr.moveLeft(); break;
  1523. case 'Shift-Left': ctrlr.selectLeft(); break;
  1524. case 'Ctrl-Left': break;
  1525. case 'Right': ctrlr.moveRight(); break;
  1526. case 'Shift-Right': ctrlr.selectRight(); break;
  1527. case 'Ctrl-Right': break;
  1528. case 'Up': ctrlr.moveUp(); break;
  1529. case 'Down': ctrlr.moveDown(); break;
  1530. case 'Shift-Up':
  1531. if (cursor[L]) {
  1532. while (cursor[L]) ctrlr.selectLeft();
  1533. } else {
  1534. ctrlr.selectLeft();
  1535. }
  1536. case 'Shift-Down':
  1537. if (cursor[R]) {
  1538. while (cursor[R]) ctrlr.selectRight();
  1539. }
  1540. else {
  1541. ctrlr.selectRight();
  1542. }
  1543. case 'Ctrl-Up': break;
  1544. case 'Ctrl-Down': break;
  1545. case 'Ctrl-Shift-Del':
  1546. case 'Ctrl-Del':
  1547. ctrlr.ctrlDeleteDir(R);
  1548. break;
  1549. case 'Shift-Del':
  1550. case 'Del':
  1551. ctrlr.deleteForward();
  1552. break;
  1553. case 'Meta-A':
  1554. case 'Ctrl-A':
  1555. ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
  1556. while (cursor[L]) ctrlr.selectLeft();
  1557. break;
  1558. default:
  1559. return;
  1560. }
  1561. e.preventDefault();
  1562. ctrlr.scrollHoriz();
  1563. };
  1564. _.moveOutOf = // called by Controller::escapeDir, moveDir
  1565. _.moveTowards = // called by Controller::moveDir
  1566. _.deleteOutOf = // called by Controller::deleteDir
  1567. _.deleteTowards = // called by Controller::deleteDir
  1568. _.unselectInto = // called by Controller::selectDir
  1569. _.selectOutOf = // called by Controller::selectDir
  1570. _.selectTowards = // called by Controller::selectDir
  1571. function() { pray('overridden or never called on this node'); };
  1572. });
  1573. Controller.open(function(_) {
  1574. this.onNotify(function(e) {
  1575. if (e === 'move' || e === 'upDown') this.show().clearSelection();
  1576. });
  1577. _.escapeDir = function(dir, key, e) {
  1578. prayDirection(dir);
  1579. var cursor = this.cursor;
  1580. // only prevent default of Tab if not in the root editable
  1581. if (cursor.parent !== this.root) e.preventDefault();
  1582. // want to be a noop if in the root editable (in fact, Tab has an unrelated
  1583. // default browser action if so)
  1584. if (cursor.parent === this.root) return;
  1585. cursor.parent.moveOutOf(dir, cursor);
  1586. return this.notify('move');
  1587. };
  1588. optionProcessors.leftRightIntoCmdGoes = function(updown) {
  1589. if (updown && updown !== 'up' && updown !== 'down') {
  1590. throw '"up" or "down" required for leftRightIntoCmdGoes option, '
  1591. + 'got "'+updown+'"';
  1592. }
  1593. return updown;
  1594. };
  1595. _.moveDir = function(dir) {
  1596. prayDirection(dir);
  1597. var cursor = this.cursor, updown = cursor.options.leftRightIntoCmdGoes;
  1598. if (cursor.selection) {
  1599. cursor.insDirOf(dir, cursor.selection.ends[dir]);
  1600. }
  1601. else if (cursor[dir]) cursor[dir].moveTowards(dir, cursor, updown);
  1602. else cursor.parent.moveOutOf(dir, cursor, updown);
  1603. return this.notify('move');
  1604. };
  1605. _.moveLeft = function() { return this.moveDir(L); };
  1606. _.moveRight = function() { return this.moveDir(R); };
  1607. /**
  1608. * moveUp and moveDown have almost identical algorithms:
  1609. * - first check left and right, if so insAtLeft/RightEnd of them
  1610. * - else check the parent's 'upOutOf'/'downOutOf' property:
  1611. * + if it's a function, call it with the cursor as the sole argument and
  1612. * use the return value as if it were the value of the property
  1613. * + if it's a Node, jump up or down into it:
  1614. * - if there is a cached Point in the block, insert there
  1615. * - else, seekHoriz within the block to the current x-coordinate (to be
  1616. * as close to directly above/below the current position as possible)
  1617. * + unless it's exactly `true`, stop bubbling
  1618. */
  1619. _.moveUp = function() { return moveUpDown(this, 'up'); };
  1620. _.moveDown = function() { return moveUpDown(this, 'down'); };
  1621. function moveUpDown(self, dir) {
  1622. var cursor = self.notify('upDown').cursor;
  1623. var dirInto = dir+'Into', dirOutOf = dir+'OutOf';
  1624. if (cursor[R][dirInto]) cursor.insAtLeftEnd(cursor[R][dirInto]);
  1625. else if (cursor[L][dirInto]) cursor.insAtRightEnd(cursor[L][dirInto]);
  1626. else {
  1627. cursor.parent.bubble(function(ancestor) {
  1628. var prop = ancestor[dirOutOf];
  1629. if (prop) {
  1630. if (typeof prop === 'function') prop = ancestor[dirOutOf](cursor);
  1631. if (prop instanceof Node) cursor.jumpUpDown(ancestor, prop);
  1632. if (prop !== true) return false;
  1633. }
  1634. });
  1635. }
  1636. return self;
  1637. }
  1638. this.onNotify(function(e) { if (e !== 'upDown') this.upDownCache = {}; });
  1639. this.onNotify(function(e) { if (e === 'edit') this.show().deleteSelection(); });
  1640. _.deleteDir = function(dir) {
  1641. prayDirection(dir);
  1642. var cursor = this.cursor;
  1643. var hadSelection = cursor.selection;
  1644. this.notify('edit'); // deletes selection if present
  1645. if (!hadSelection) {
  1646. if (cursor[dir]) cursor[dir].deleteTowards(dir, cursor);
  1647. else cursor.parent.deleteOutOf(dir, cursor);
  1648. }
  1649. if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
  1650. if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
  1651. cursor.parent.bubble('reflow');
  1652. return this;
  1653. };
  1654. _.ctrlDeleteDir = function(dir) {
  1655. prayDirection(dir);
  1656. var cursor = this.cursor;
  1657. if (!cursor[L] || cursor.selection) return ctrlr.deleteDir();
  1658. this.notify('edit');
  1659. Fragment(cursor.parent.ends[L], cursor[L]).remove();
  1660. cursor.insAtDirEnd(L, cursor.parent);
  1661. if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
  1662. if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
  1663. cursor.parent.bubble('reflow');
  1664. return this;
  1665. };
  1666. _.backspace = function() { return this.deleteDir(L); };
  1667. _.deleteForward = function() { return this.deleteDir(R); };
  1668. this.onNotify(function(e) { if (e !== 'select') this.endSelection(); });
  1669. _.selectDir = function(dir) {
  1670. var cursor = this.notify('select').cursor, seln = cursor.selection;
  1671. prayDirection(dir);
  1672. if (!cursor.anticursor) cursor.startSelection();
  1673. var node = cursor[dir];
  1674. if (node) {
  1675. // "if node we're selecting towards is inside selection (hence retracting)
  1676. // and is on the *far side* of the selection (hence is only node selected)
  1677. // and the anticursor is *inside* that node, not just on the other side"
  1678. if (seln && seln.ends[dir] === node && cursor.anticursor[-dir] !== node) {
  1679. node.unselectInto(dir, cursor);
  1680. }
  1681. else node.selectTowards(dir, cursor);
  1682. }
  1683. else cursor.parent.selectOutOf(dir, cursor);
  1684. cursor.clearSelection();
  1685. cursor.select() || cursor.show();
  1686. };
  1687. _.selectLeft = function() { return this.selectDir(L); };
  1688. _.selectRight = function() { return this.selectDir(R); };
  1689. });
  1690. // Parser MathCommand
  1691. var latexMathParser = (function() {
  1692. function commandToBlock(cmd) {
  1693. var block = MathBlock();
  1694. cmd.adopt(block, 0, 0);
  1695. return block;
  1696. }
  1697. function joinBlocks(blocks) {
  1698. var firstBlock = blocks[0] || MathBlock();
  1699. for (var i = 1; i < blocks.length; i += 1) {
  1700. blocks[i].children().adopt(firstBlock, firstBlock.ends[R], 0);
  1701. }
  1702. return firstBlock;
  1703. }
  1704. var string = Parser.string;
  1705. var regex = Parser.regex;
  1706. var letter = Parser.letter;
  1707. var any = Parser.any;
  1708. var optWhitespace = Parser.optWhitespace;
  1709. var succeed = Parser.succeed;
  1710. var fail = Parser.fail;
  1711. // Parsers yielding either MathCommands, or Fragments of MathCommands
  1712. // (either way, something that can be adopted by a MathBlock)
  1713. var variable = letter.map(function(c) { return Letter(c); });
  1714. var symbol = regex(/^[^${}\\_^]/).map(function(c) { return VanillaSymbol(c); });
  1715. var controlSequence =
  1716. regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write
  1717. .or(string('\\').then(
  1718. regex(/^[a-z]+/i)
  1719. .or(regex(/^\s+/).result(' '))
  1720. .or(any)
  1721. )).then(function(ctrlSeq) {
  1722. var cmdKlass = LatexCmds[ctrlSeq];
  1723. if (cmdKlass) {
  1724. return cmdKlass(ctrlSeq).parser();
  1725. }
  1726. else {
  1727. return fail('unknown command: \\'+ctrlSeq);
  1728. }
  1729. })
  1730. ;
  1731. var command =
  1732. controlSequence
  1733. .or(variable)
  1734. .or(symbol)
  1735. ;
  1736. // Parsers yielding MathBlocks
  1737. var mathGroup = string('{').then(function() { return mathSequence; }).skip(string('}'));
  1738. var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock)));
  1739. var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace);
  1740. var optMathBlock =
  1741. string('[').then(
  1742. mathBlock.then(function(block) {
  1743. return block.join('latex') !== ']' ? succeed(block) : fail();
  1744. })
  1745. .many().map(joinBlocks).skip(optWhitespace)
  1746. ).skip(string(']'))
  1747. ;
  1748. var latexMath = mathSequence;
  1749. latexMath.block = mathBlock;
  1750. latexMath.optBlock = optMathBlock;
  1751. return latexMath;
  1752. })();
  1753. Controller.open(function(_, super_) {
  1754. _.exportLatex = function() {
  1755. return this.root.latex().replace(/(\\[a-z]+) (?![a-z])/ig,'$1');
  1756. };
  1757. _.writeLatex = function(latex) {
  1758. var cursor = this.notify('edit').cursor;
  1759. var all = Parser.all;
  1760. var eof = Parser.eof;
  1761. var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
  1762. if (block && !block.isEmpty()) {
  1763. block.children().adopt(cursor.parent, cursor[L], cursor[R]);
  1764. var jQ = block.jQize();
  1765. jQ.insertBefore(cursor.jQ);
  1766. cursor[L] = block.ends[R];
  1767. block.finalizeInsert(cursor.options, cursor);
  1768. if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
  1769. if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
  1770. cursor.parent.bubble('reflow');
  1771. }
  1772. return this;
  1773. };
  1774. _.renderLatexMath = function(latex) {
  1775. var root = this.root, cursor = this.cursor;
  1776. var all = Parser.all;
  1777. var eof = Parser.eof;
  1778. var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
  1779. root.eachChild('postOrder', 'dispose');
  1780. root.ends[L] = root.ends[R] = 0;
  1781. if (block) {
  1782. block.children().adopt(root, 0, 0);
  1783. }
  1784. var jQ = root.jQ;
  1785. if (block) {
  1786. var html = block.join('html');
  1787. jQ.html(html);
  1788. root.jQize(jQ.children());
  1789. root.finalizeInsert(cursor.options);
  1790. }
  1791. else {
  1792. jQ.empty();
  1793. }
  1794. delete cursor.selection;
  1795. cursor.insAtRightEnd(root);
  1796. };
  1797. _.renderLatexText = function(latex) {
  1798. var root = this.root, cursor = this.cursor;
  1799. root.jQ.children().slice(1).remove();
  1800. root.eachChild('postOrder', 'dispose');
  1801. root.ends[L] = root.ends[R] = 0;
  1802. delete cursor.selection;
  1803. cursor.show().insAtRightEnd(root);
  1804. var regex = Parser.regex;
  1805. var string = Parser.string;
  1806. var eof = Parser.eof;
  1807. var all = Parser.all;
  1808. // Parser RootMathCommand
  1809. var mathMode = string('$').then(latexMathParser)
  1810. // because TeX is insane, math mode doesn't necessarily
  1811. // have to end. So we allow for the case that math mode
  1812. // continues to the end of the stream.
  1813. .skip(string('$').or(eof))
  1814. .map(function(block) {
  1815. // HACK FIXME: this shouldn't have to have access to cursor
  1816. var rootMathCommand = RootMathCommand(cursor);
  1817. rootMathCommand.createBlocks();
  1818. var rootMathBlock = rootMathCommand.ends[L];
  1819. block.children().adopt(rootMathBlock, 0, 0);
  1820. return rootMathCommand;
  1821. })
  1822. ;
  1823. var escapedDollar = string('\\$').result('$');
  1824. var textChar = escapedDollar.or(regex(/^[^$]/)).map(VanillaSymbol);
  1825. var latexText = mathMode.or(textChar).many();
  1826. var commands = latexText.skip(eof).or(all.result(false)).parse(latex);
  1827. if (commands) {
  1828. for (var i = 0; i < commands.length; i += 1) {
  1829. commands[i].adopt(root, root.ends[R], 0);
  1830. }
  1831. root.jQize().appendTo(root.jQ);
  1832. root.finalizeInsert(cursor.options);
  1833. }
  1834. };
  1835. });
  1836. /********************************************************
  1837. * Deals with mouse events for clicking, drag-to-select
  1838. *******************************************************/
  1839. Controller.open(function(_) {
  1840. _.delegateMouseEvents = function() {
  1841. var ultimateRootjQ = this.root.jQ;
  1842. //drag-to-select event handling
  1843. this.container.bind('mousedown.mathquill', function(e) {
  1844. var rootjQ = $(e.target).closest('.mq-root-block');
  1845. var root = Node.byId[rootjQ.attr(mqBlockId) || ultimateRootjQ.attr(mqBlockId)];
  1846. var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink;
  1847. var textareaSpan = ctrlr.textareaSpan, textarea = ctrlr.textarea;
  1848. var target;
  1849. function mousemove(e) { target = $(e.target); }
  1850. function docmousemove(e) {
  1851. if (!cursor.anticursor) cursor.startSelection();
  1852. ctrlr.seek(target, e.pageX, e.pageY).cursor.select();
  1853. target = undefined;
  1854. }
  1855. // outside rootjQ, the MathQuill node corresponding to the target (if any)
  1856. // won't be inside this root, so don't mislead Controller::seek with it
  1857. function mouseup(e) {
  1858. cursor.blink = blink;
  1859. if (!cursor.selection) {
  1860. if (ctrlr.editable) {
  1861. cursor.show();
  1862. }
  1863. else {
  1864. textareaSpan.detach();
  1865. }
  1866. }
  1867. // delete the mouse handlers now that we're not dragging anymore
  1868. rootjQ.unbind('mousemove', mousemove);
  1869. $(e.target.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup', mouseup);
  1870. }
  1871. if (ctrlr.blurred) {
  1872. if (!ctrlr.editable) rootjQ.prepend(textareaSpan);
  1873. textarea.focus();
  1874. }
  1875. e.preventDefault(); // doesn't work in IE≤8, but it's a one-line fix:
  1876. e.target.unselectable = true; // http://jsbin.com/yagekiji/1
  1877. cursor.blink = noop;
  1878. ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection();
  1879. rootjQ.mousemove(mousemove);
  1880. $(e.target.ownerDocument).mousemove(docmousemove).mouseup(mouseup);
  1881. // listen on document not just body to not only hear about mousemove and
  1882. // mouseup on page outside field, but even outside page, except iframes: https://github.com/mathquill/mathquill/commit/8c50028afcffcace655d8ae2049f6e02482346c5#commitcomment-6175800
  1883. });
  1884. }
  1885. });
  1886. Controller.open(function(_) {
  1887. _.seek = function(target, pageX, pageY) {
  1888. var cursor = this.notify('select').cursor;
  1889. if (target) {
  1890. var nodeId = target.attr(mqBlockId) || target.attr(mqCmdId);
  1891. if (!nodeId) {
  1892. var targetParent = target.parent();
  1893. nodeId = targetParent.attr(mqBlockId) || targetParent.attr(mqCmdId);
  1894. }
  1895. }
  1896. var node = nodeId ? Node.byId[nodeId] : this.root;
  1897. pray('nodeId is the id of some Node that exists', node);
  1898. // don't clear selection until after getting node from target, in case
  1899. // target was selection span, otherwise target will have no parent and will
  1900. // seek from root, which is less accurate (e.g. fraction)
  1901. cursor.clearSelection().show();
  1902. node.seek(pageX, cursor);
  1903. this.scrollHoriz(); // before .selectFrom when mouse-selecting, so
  1904. // always hits no-selection case in scrollHoriz and scrolls slower
  1905. return this;
  1906. };
  1907. });
  1908. /***********************************************
  1909. * Horizontal panning for editable fields that
  1910. * overflow their width
  1911. **********************************************/
  1912. Controller.open(function(_) {
  1913. _.scrollHoriz = function() {
  1914. var cursor = this.cursor, seln = cursor.selection;
  1915. var rootRect = this.root.jQ[0].getBoundingClientRect();
  1916. if (!seln) {
  1917. var x = cursor.jQ[0].getBoundingClientRect().left;
  1918. if (x > rootRect.right - 20) var scrollBy = x - (rootRect.right - 20);
  1919. else if (x < rootRect.left + 20) var scrollBy = x - (rootRect.left + 20);
  1920. else return;
  1921. }
  1922. else {
  1923. var rect = seln.jQ[0].getBoundingClientRect();
  1924. var overLeft = rect.left - (rootRect.left + 20);
  1925. var overRight = rect.right - (rootRect.right - 20);
  1926. if (seln.ends[L] === cursor[R]) {
  1927. if (overLeft < 0) var scrollBy = overLeft;
  1928. else if (overRight > 0) {
  1929. if (rect.left - overRight < rootRect.left + 20) var scrollBy = overLeft;
  1930. else var scrollBy = overRight;
  1931. }
  1932. else return;
  1933. }
  1934. else {
  1935. if (overRight > 0) var scrollBy = overRight;
  1936. else if (overLeft < 0) {
  1937. if (rect.right - overLeft > rootRect.right - 20) var scrollBy = overRight;
  1938. else var scrollBy = overLeft;
  1939. }
  1940. else return;
  1941. }
  1942. }
  1943. this.root.jQ.stop().animate({ scrollLeft: '+=' + scrollBy}, 100);
  1944. };
  1945. });
  1946. /*********************************************
  1947. * Manage the MathQuill instance's textarea
  1948. * (as owned by the Controller)
  1949. ********************************************/
  1950. Controller.open(function(_) {
  1951. Options.p.substituteTextarea = function() {
  1952. return $('<textarea autocapitalize=off autocomplete=off autocorrect=off ' +
  1953. 'spellcheck=false x-palm-disable-ste-all=true />')[0];
  1954. };
  1955. _.createTextarea = function() {
  1956. var textareaSpan = this.textareaSpan = $('<span class="mq-textarea"></span>'),
  1957. textarea = this.options.substituteTextarea();
  1958. if (!textarea.nodeType) {
  1959. throw 'substituteTextarea() must return a DOM element, got ' + textarea;
  1960. }
  1961. textarea = this.textarea = $(textarea).appendTo(textareaSpan);
  1962. var ctrlr = this;
  1963. ctrlr.cursor.selectionChanged = function() { ctrlr.selectionChanged(); };
  1964. ctrlr.container.bind('copy', function() { ctrlr.setTextareaSelection(); });
  1965. };
  1966. _.selectionChanged = function() {
  1967. var ctrlr = this;
  1968. forceIERedraw(ctrlr.container[0]);
  1969. // throttle calls to setTextareaSelection(), because setting textarea.value
  1970. // and/or calling textarea.select() can have anomalously bad performance:
  1971. // https://github.com/mathquill/mathquill/issues/43#issuecomment-1399080
  1972. if (ctrlr.textareaSelectionTimeout === undefined) {
  1973. ctrlr.textareaSelectionTimeout = setTimeout(function() {
  1974. ctrlr.setTextareaSelection();
  1975. });
  1976. }
  1977. };
  1978. _.setTextareaSelection = function() {
  1979. this.textareaSelectionTimeout = undefined;
  1980. var latex = '';
  1981. if (this.cursor.selection) {
  1982. latex = this.cursor.selection.join('latex');
  1983. if (this.options.statelessClipboard) {
  1984. // FIXME: like paste, only this works for math fields; should ask parent
  1985. latex = '$' + latex + '$';
  1986. }
  1987. }
  1988. this.selectFn(latex);
  1989. };
  1990. _.staticMathTextareaEvents = function() {
  1991. var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
  1992. textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
  1993. this.container.prepend('<span class="mq-selectable">$'+ctrlr.exportLatex()+'$</span>');
  1994. ctrlr.blurred = true;
  1995. textarea.bind('cut paste', false)
  1996. .focus(function() { ctrlr.blurred = false; }).blur(function() {
  1997. if (cursor.selection) cursor.selection.clear();
  1998. setTimeout(detach); //detaching during blur explodes in WebKit
  1999. });
  2000. function detach() {
  2001. textareaSpan.detach();
  2002. ctrlr.blurred = true;
  2003. }
  2004. ctrlr.selectFn = function(text) {
  2005. textarea.val(text);
  2006. if (text) textarea.select();
  2007. };
  2008. };
  2009. _.editablesTextareaEvents = function() {
  2010. var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
  2011. textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
  2012. var keyboardEventsShim = saneKeyboardEvents(textarea, this);
  2013. this.selectFn = function(text) { keyboardEventsShim.select(text); };
  2014. this.container.prepend(textareaSpan)
  2015. .on('cut', function(e) {
  2016. if (cursor.selection) {
  2017. setTimeout(function() {
  2018. ctrlr.notify('edit'); // deletes selection if present
  2019. cursor.parent.bubble('reflow');
  2020. });
  2021. }
  2022. });
  2023. this.focusBlurEvents();
  2024. };
  2025. _.typedText = function(ch) {
  2026. if (ch === '\n') return this.handle('enter');
  2027. var cursor = this.notify().cursor;
  2028. cursor.parent.write(cursor, ch);
  2029. this.scrollHoriz();
  2030. };
  2031. _.paste = function(text) {
  2032. // TODO: document `statelessClipboard` config option in README, after
  2033. // making it work like it should, that is, in both text and math mode
  2034. // (currently only works in math fields, so worse than pointless, it
  2035. // only gets in the way by \text{}-ifying pasted stuff and $-ifying
  2036. // cut/copied LaTeX)
  2037. if (this.options.statelessClipboard) {
  2038. if (text.slice(0,1) === '$' && text.slice(-1) === '$') {
  2039. text = text.slice(1, -1);
  2040. }
  2041. else {
  2042. text = '\\text{'+text+'}';
  2043. }
  2044. }
  2045. // FIXME: this always inserts math or a TextBlock, even in a RootTextBlock
  2046. this.writeLatex(text).cursor.show();
  2047. };
  2048. });
  2049. /*************************************************
  2050. * Abstract classes of math blocks and commands.
  2051. ************************************************/
  2052. /**
  2053. * Math tree node base class.
  2054. * Some math-tree-specific extensions to Node.
  2055. * Both MathBlock's and MathCommand's descend from it.
  2056. */
  2057. var MathElement = P(Node, function(_, super_) {
  2058. _.finalizeInsert = function(options, cursor) { // `cursor` param is only for
  2059. // SupSub::contactWeld, and is deliberately only passed in by writeLatex,
  2060. // see ea7307eb4fac77c149a11ffdf9a831df85247693
  2061. var self = this;
  2062. self.postOrder('finalizeTree', options);
  2063. self.postOrder('contactWeld', cursor);
  2064. // note: this order is important.
  2065. // empty elements need the empty box provided by blur to
  2066. // be present in order for their dimensions to be measured
  2067. // correctly by 'reflow' handlers.
  2068. self.postOrder('blur');
  2069. self.postOrder('reflow');
  2070. if (self[R].siblingCreated) self[R].siblingCreated(options, L);
  2071. if (self[L].siblingCreated) self[L].siblingCreated(options, R);
  2072. self.bubble('reflow');
  2073. };
  2074. });
  2075. /**
  2076. * Commands and operators, like subscripts, exponents, or fractions.
  2077. * Descendant commands are organized into blocks.
  2078. */
  2079. var MathCommand = P(MathElement, function(_, super_) {
  2080. _.init = function(ctrlSeq, htmlTemplate, textTemplate) {
  2081. var cmd = this;
  2082. super_.init.call(cmd);
  2083. if (!cmd.ctrlSeq) cmd.ctrlSeq = ctrlSeq;
  2084. if (htmlTemplate) cmd.htmlTemplate = htmlTemplate;
  2085. if (textTemplate) cmd.textTemplate = textTemplate;
  2086. };
  2087. // obvious methods
  2088. _.replaces = function(replacedFragment) {
  2089. replacedFragment.disown();
  2090. this.replacedFragment = replacedFragment;
  2091. };
  2092. _.isEmpty = function() {
  2093. return this.foldChildren(true, function(isEmpty, child) {
  2094. return isEmpty && child.isEmpty();
  2095. });
  2096. };
  2097. _.parser = function() {
  2098. var block = latexMathParser.block;
  2099. var self = this;
  2100. return block.times(self.numBlocks()).map(function(blocks) {
  2101. self.blocks = blocks;
  2102. for (var i = 0; i < blocks.length; i += 1) {
  2103. blocks[i].adopt(self, self.ends[R], 0);
  2104. }
  2105. return self;
  2106. });
  2107. };
  2108. // createLeftOf(cursor) and the methods it calls
  2109. _.createLeftOf = function(cursor) {
  2110. var cmd = this;
  2111. var replacedFragment = cmd.replacedFragment;
  2112. cmd.createBlocks();
  2113. super_.createLeftOf.call(cmd, cursor);
  2114. if (replacedFragment) {
  2115. replacedFragment.adopt(cmd.ends[L], 0, 0);
  2116. replacedFragment.jQ.appendTo(cmd.ends[L].jQ);
  2117. }
  2118. cmd.finalizeInsert(cursor.options);
  2119. cmd.placeCursor(cursor);
  2120. };
  2121. _.createBlocks = function() {
  2122. var cmd = this,
  2123. numBlocks = cmd.numBlocks(),
  2124. blocks = cmd.blocks = Array(numBlocks);
  2125. for (var i = 0; i < numBlocks; i += 1) {
  2126. var newBlock = blocks[i] = MathBlock();
  2127. newBlock.adopt(cmd, cmd.ends[R], 0);
  2128. }
  2129. };
  2130. _.placeCursor = function(cursor) {
  2131. //insert the cursor at the right end of the first empty child, searching
  2132. //left-to-right, or if none empty, the right end child
  2133. cursor.insAtRightEnd(this.foldChildren(this.ends[L], function(leftward, child) {
  2134. return leftward.isEmpty() ? leftward : child;
  2135. }));
  2136. };
  2137. // editability methods: called by the cursor for editing, cursor movements,
  2138. // and selection of the MathQuill tree, these all take in a direction and
  2139. // the cursor
  2140. _.moveTowards = function(dir, cursor, updown) {
  2141. var updownInto = updown && this[updown+'Into'];
  2142. cursor.insAtDirEnd(-dir, updownInto || this.ends[-dir]);
  2143. };
  2144. _.deleteTowards = function(dir, cursor) {
  2145. if (this.isEmpty()) cursor[dir] = this.remove()[dir];
  2146. else this.moveTowards(dir, cursor, null);
  2147. };
  2148. _.selectTowards = function(dir, cursor) {
  2149. cursor[-dir] = this;
  2150. cursor[dir] = this[dir];
  2151. };
  2152. _.selectChildren = function() {
  2153. return Selection(this, this);
  2154. };
  2155. _.unselectInto = function(dir, cursor) {
  2156. cursor.insAtDirEnd(-dir, cursor.anticursor.ancestors[this.id]);
  2157. };
  2158. _.seek = function(pageX, cursor) {
  2159. function getBounds(node) {
  2160. var bounds = {}
  2161. bounds[L] = node.jQ.offset().left;
  2162. bounds[R] = bounds[L] + node.jQ.outerWidth();
  2163. return bounds;
  2164. }
  2165. var cmd = this;
  2166. var cmdBounds = getBounds(cmd);
  2167. if (pageX < cmdBounds[L]) return cursor.insLeftOf(cmd);
  2168. if (pageX > cmdBounds[R]) return cursor.insRightOf(cmd);
  2169. var leftLeftBound = cmdBounds[L];
  2170. cmd.eachChild(function(block) {
  2171. var blockBounds = getBounds(block);
  2172. if (pageX < blockBounds[L]) {
  2173. // closer to this block's left bound, or the bound left of that?
  2174. if (pageX - leftLeftBound < blockBounds[L] - pageX) {
  2175. if (block[L]) cursor.insAtRightEnd(block[L]);
  2176. else cursor.insLeftOf(cmd);
  2177. }
  2178. else cursor.insAtLeftEnd(block);
  2179. return false;
  2180. }
  2181. else if (pageX > blockBounds[R]) {
  2182. if (block[R]) leftLeftBound = blockBounds[R]; // continue to next block
  2183. else { // last (rightmost) block
  2184. // closer to this block's right bound, or the cmd's right bound?
  2185. if (cmdBounds[R] - pageX < pageX - blockBounds[R]) {
  2186. cursor.insRightOf(cmd);
  2187. }
  2188. else cursor.insAtRightEnd(block);
  2189. }
  2190. }
  2191. else {
  2192. block.seek(pageX, cursor);
  2193. return false;
  2194. }
  2195. });
  2196. }
  2197. // methods involved in creating and cross-linking with HTML DOM nodes
  2198. /*
  2199. They all expect an .htmlTemplate like
  2200. '<span>&0</span>'
  2201. or
  2202. '<span><span>&0</span><span>&1</span></span>'
  2203. See html.test.js for more examples.
  2204. Requirements:
  2205. - For each block of the command, there must be exactly one "block content
  2206. marker" of the form '&<number>' where <number> is the 0-based index of the
  2207. block. (Like the LaTeX \newcommand syntax, but with a 0-based rather than
  2208. 1-based index, because JavaScript because C because Dijkstra.)
  2209. - The block content marker must be the sole contents of the containing
  2210. element, there can't even be surrounding whitespace, or else we can't
  2211. guarantee sticking to within the bounds of the block content marker when
  2212. mucking with the HTML DOM.
  2213. - The HTML not only must be well-formed HTML (of course), but also must
  2214. conform to the XHTML requirements on tags, specifically all tags must
  2215. either be self-closing (like '<br/>') or come in matching pairs.
  2216. Close tags are never optional.
  2217. Note that &<number> isn't well-formed HTML; if you wanted a literal '&123',
  2218. your HTML template would have to have '&amp;123'.
  2219. */
  2220. _.numBlocks = function() {
  2221. var matches = this.htmlTemplate.match(/&\d+/g);
  2222. return matches ? matches.length : 0;
  2223. };
  2224. _.html = function() {
  2225. // Render the entire math subtree rooted at this command, as HTML.
  2226. // Expects .createBlocks() to have been called already, since it uses the
  2227. // .blocks array of child blocks.
  2228. //
  2229. // See html.test.js for example templates and intended outputs.
  2230. //
  2231. // Given an .htmlTemplate as described above,
  2232. // - insert the mathquill-command-id attribute into all top-level tags,
  2233. // which will be used to set this.jQ in .jQize().
  2234. // This is straightforward:
  2235. // * tokenize into tags and non-tags
  2236. // * loop through top-level tokens:
  2237. // * add #cmdId attribute macro to top-level self-closing tags
  2238. // * else add #cmdId attribute macro to top-level open tags
  2239. // * skip the matching top-level close tag and all tag pairs
  2240. // in between
  2241. // - for each block content marker,
  2242. // + replace it with the contents of the corresponding block,
  2243. // rendered as HTML
  2244. // + insert the mathquill-block-id attribute into the containing tag
  2245. // This is even easier, a quick regex replace, since block tags cannot
  2246. // contain anything besides the block content marker.
  2247. //
  2248. // Two notes:
  2249. // - The outermost loop through top-level tokens should never encounter any
  2250. // top-level close tags, because we should have first encountered a
  2251. // matching top-level open tag, all inner tags should have appeared in
  2252. // matching pairs and been skipped, and then we should have skipped the
  2253. // close tag in question.
  2254. // - All open tags should have matching close tags, which means our inner
  2255. // loop should always encounter a close tag and drop nesting to 0. If
  2256. // a close tag is missing, the loop will continue until i >= tokens.length
  2257. // and token becomes undefined. This will not infinite loop, even in
  2258. // production without pray(), because it will then TypeError on .slice().
  2259. var cmd = this;
  2260. var blocks = cmd.blocks;
  2261. var cmdId = ' mathquill-command-id=' + cmd.id;
  2262. var tokens = cmd.htmlTemplate.match(/<[^<>]+>|[^<>]+/g);
  2263. pray('no unmatched angle brackets', tokens.join('') === this.htmlTemplate);
  2264. // add cmdId to all top-level tags
  2265. for (var i = 0, token = tokens[0]; token; i += 1, token = tokens[i]) {
  2266. // top-level self-closing tags
  2267. if (token.slice(-2) === '/>') {
  2268. tokens[i] = token.slice(0,-2) + cmdId + '/>';
  2269. }
  2270. // top-level open tags
  2271. else if (token.charAt(0) === '<') {
  2272. pray('not an unmatched top-level close tag', token.charAt(1) !== '/');
  2273. tokens[i] = token.slice(0,-1) + cmdId + '>';
  2274. // skip matching top-level close tag and all tag pairs in between
  2275. var nesting = 1;
  2276. do {
  2277. i += 1, token = tokens[i];
  2278. pray('no missing close tags', token);
  2279. // close tags
  2280. if (token.slice(0,2) === '</') {
  2281. nesting -= 1;
  2282. }
  2283. // non-self-closing open tags
  2284. else if (token.charAt(0) === '<' && token.slice(-2) !== '/>') {
  2285. nesting += 1;
  2286. }
  2287. } while (nesting > 0);
  2288. }
  2289. }
  2290. return tokens.join('').replace(/>&(\d+)/g, function($0, $1) {
  2291. return ' mathquill-block-id=' + blocks[$1].id + '>' + blocks[$1].join('html');
  2292. });
  2293. };
  2294. // methods to export a string representation of the math tree
  2295. _.latex = function() {
  2296. return this.foldChildren(this.ctrlSeq, function(latex, child) {
  2297. return latex + '{' + (child.latex() || ' ') + '}';
  2298. });
  2299. };
  2300. _.textTemplate = [''];
  2301. _.text = function() {
  2302. var cmd = this, i = 0;
  2303. return cmd.foldChildren(cmd.textTemplate[i], function(text, child) {
  2304. i += 1;
  2305. var child_text = child.text();
  2306. if (text && cmd.textTemplate[i] === '('
  2307. && child_text[0] === '(' && child_text.slice(-1) === ')')
  2308. return text + child_text.slice(1, -1) + cmd.textTemplate[i];
  2309. return text + child.text() + (cmd.textTemplate[i] || '');
  2310. });
  2311. };
  2312. });
  2313. /**
  2314. * Lightweight command without blocks or children.
  2315. */
  2316. var Symbol = P(MathCommand, function(_, super_) {
  2317. _.init = function(ctrlSeq, html, text) {
  2318. if (!text) text = ctrlSeq && ctrlSeq.length > 1 ? ctrlSeq.slice(1) : ctrlSeq;
  2319. super_.init.call(this, ctrlSeq, html, [ text ]);
  2320. };
  2321. _.parser = function() { return Parser.succeed(this); };
  2322. _.numBlocks = function() { return 0; };
  2323. _.replaces = function(replacedFragment) {
  2324. replacedFragment.remove();
  2325. };
  2326. _.createBlocks = noop;
  2327. _.moveTowards = function(dir, cursor) {
  2328. cursor.jQ.insDirOf(dir, this.jQ);
  2329. cursor[-dir] = this;
  2330. cursor[dir] = this[dir];
  2331. };
  2332. _.deleteTowards = function(dir, cursor) {
  2333. cursor[dir] = this.remove()[dir];
  2334. };
  2335. _.seek = function(pageX, cursor) {
  2336. // insert at whichever side the click was closer to
  2337. if (pageX - this.jQ.offset().left < this.jQ.outerWidth()/2)
  2338. cursor.insLeftOf(this);
  2339. else
  2340. cursor.insRightOf(this);
  2341. };
  2342. _.latex = function(){ return this.ctrlSeq; };
  2343. _.text = function(){ return this.textTemplate; };
  2344. _.placeCursor = noop;
  2345. _.isEmpty = function(){ return true; };
  2346. });
  2347. var VanillaSymbol = P(Symbol, function(_, super_) {
  2348. _.init = function(ch, html) {
  2349. super_.init.call(this, ch, '<span>'+(html || ch)+'</span>');
  2350. };
  2351. });
  2352. var BinaryOperator = P(Symbol, function(_, super_) {
  2353. _.init = function(ctrlSeq, html, text) {
  2354. super_.init.call(this,
  2355. ctrlSeq, '<span class="mq-binary-operator">'+html+'</span>', text
  2356. );
  2357. };
  2358. });
  2359. /**
  2360. * Children and parent of MathCommand's. Basically partitions all the
  2361. * symbols and operators that descend (in the Math DOM tree) from
  2362. * ancestor operators.
  2363. */
  2364. var MathBlock = P(MathElement, function(_, super_) {
  2365. _.join = function(methodName) {
  2366. return this.foldChildren('', function(fold, child) {
  2367. return fold + child[methodName]();
  2368. });
  2369. };
  2370. _.html = function() { return this.join('html'); };
  2371. _.latex = function() { return this.join('latex'); };
  2372. _.text = function() {
  2373. return (this.ends[L] === this.ends[R] && this.ends[L] !== 0) ?
  2374. this.ends[L].text() :
  2375. this.join('text')
  2376. ;
  2377. };
  2378. _.keystroke = function(key, e, ctrlr) {
  2379. if (ctrlr.options.spaceBehavesLikeTab
  2380. && (key === 'Spacebar' || key === 'Shift-Spacebar')) {
  2381. e.preventDefault();
  2382. ctrlr.escapeDir(key === 'Shift-Spacebar' ? L : R, key, e);
  2383. return;
  2384. }
  2385. return super_.keystroke.apply(this, arguments);
  2386. };
  2387. // editability methods: called by the cursor for editing, cursor movements,
  2388. // and selection of the MathQuill tree, these all take in a direction and
  2389. // the cursor
  2390. _.moveOutOf = function(dir, cursor, updown) {
  2391. var updownInto = updown && this.parent[updown+'Into'];
  2392. if (!updownInto && this[dir]) cursor.insAtDirEnd(-dir, this[dir]);
  2393. else cursor.insDirOf(dir, this.parent);
  2394. };
  2395. _.selectOutOf = function(dir, cursor) {
  2396. cursor.insDirOf(dir, this.parent);
  2397. };
  2398. _.deleteOutOf = function(dir, cursor) {
  2399. cursor.unwrapGramp();
  2400. };
  2401. _.seek = function(pageX, cursor) {
  2402. var node = this.ends[R];
  2403. if (!node || node.jQ.offset().left + node.jQ.outerWidth() < pageX) {
  2404. return cursor.insAtRightEnd(this);
  2405. }
  2406. if (pageX < this.ends[L].jQ.offset().left) return cursor.insAtLeftEnd(this);
  2407. while (pageX < node.jQ.offset().left) node = node[L];
  2408. return node.seek(pageX, cursor);
  2409. };
  2410. _.chToCmd = function(ch) {
  2411. var cons;
  2412. // exclude f because it gets a dedicated command with more spacing
  2413. if (ch.match(/^[a-eg-zA-Z]$/))
  2414. return Letter(ch);
  2415. else if (/^\d$/.test(ch))
  2416. return Digit(ch);
  2417. else if (cons = CharCmds[ch] || LatexCmds[ch])
  2418. return cons(ch);
  2419. else
  2420. return VanillaSymbol(ch);
  2421. };
  2422. _.write = function(cursor, ch) {
  2423. var cmd = this.chToCmd(ch);
  2424. if (cursor.selection) cmd.replaces(cursor.replaceSelection());
  2425. cmd.createLeftOf(cursor.show());
  2426. };
  2427. _.focus = function() {
  2428. this.jQ.addClass('mq-hasCursor');
  2429. this.jQ.removeClass('mq-empty');
  2430. return this;
  2431. };
  2432. _.blur = function() {
  2433. this.jQ.removeClass('mq-hasCursor');
  2434. if (this.isEmpty())
  2435. this.jQ.addClass('mq-empty');
  2436. return this;
  2437. };
  2438. });
  2439. API.StaticMath = function(APIClasses) {
  2440. return P(APIClasses.AbstractMathQuill, function(_, super_) {
  2441. this.RootBlock = MathBlock;
  2442. _.__mathquillify = function() {
  2443. super_.__mathquillify.call(this, 'mq-math-mode');
  2444. this.__controller.delegateMouseEvents();
  2445. this.__controller.staticMathTextareaEvents();
  2446. return this;
  2447. };
  2448. _.init = function() {
  2449. super_.init.apply(this, arguments);
  2450. this.__controller.root.postOrder(
  2451. 'registerInnerField', this.innerFields = [], APIClasses.MathField);
  2452. };
  2453. _.latex = function() {
  2454. var returned = super_.latex.apply(this, arguments);
  2455. if (arguments.length > 0) {
  2456. this.__controller.root.postOrder(
  2457. 'registerInnerField', this.innerFields = [], APIClasses.MathField);
  2458. }
  2459. return returned;
  2460. };
  2461. });
  2462. };
  2463. var RootMathBlock = P(MathBlock, RootBlockMixin);
  2464. API.MathField = function(APIClasses) {
  2465. return P(APIClasses.EditableField, function(_, super_) {
  2466. this.RootBlock = RootMathBlock;
  2467. _.__mathquillify = function(opts, interfaceVersion) {
  2468. this.config(opts);
  2469. if (interfaceVersion > 1) this.__controller.root.reflow = noop;
  2470. super_.__mathquillify.call(this, 'mq-editable-field mq-math-mode');
  2471. delete this.__controller.root.reflow;
  2472. return this;
  2473. };
  2474. });
  2475. };
  2476. /*************************************************
  2477. * Abstract classes of text blocks
  2478. ************************************************/
  2479. /**
  2480. * Blocks of plain text, with one or two TextPiece's as children.
  2481. * Represents flat strings of typically serif-font Roman characters, as
  2482. * opposed to hierchical, nested, tree-structured math.
  2483. * Wraps a single HTMLSpanElement.
  2484. */
  2485. var TextBlock = P(Node, function(_, super_) {
  2486. _.ctrlSeq = '\\text';
  2487. _.replaces = function(replacedText) {
  2488. if (replacedText instanceof Fragment)
  2489. this.replacedText = replacedText.remove().jQ.text();
  2490. else if (typeof replacedText === 'string')
  2491. this.replacedText = replacedText;
  2492. };
  2493. _.jQadd = function(jQ) {
  2494. super_.jQadd.call(this, jQ);
  2495. if (this.ends[L]) this.ends[L].jQadd(this.jQ[0].firstChild);
  2496. };
  2497. _.createLeftOf = function(cursor) {
  2498. var textBlock = this;
  2499. super_.createLeftOf.call(this, cursor);
  2500. if (textBlock[R].siblingCreated) textBlock[R].siblingCreated(cursor.options, L);
  2501. if (textBlock[L].siblingCreated) textBlock[L].siblingCreated(cursor.options, R);
  2502. textBlock.bubble('reflow');
  2503. cursor.insAtRightEnd(textBlock);
  2504. if (textBlock.replacedText)
  2505. for (var i = 0; i < textBlock.replacedText.length; i += 1)
  2506. textBlock.write(cursor, textBlock.replacedText.charAt(i));
  2507. };
  2508. _.parser = function() {
  2509. var textBlock = this;
  2510. // TODO: correctly parse text mode
  2511. var string = Parser.string;
  2512. var regex = Parser.regex;
  2513. var optWhitespace = Parser.optWhitespace;
  2514. return optWhitespace
  2515. .then(string('{')).then(regex(/^[^}]*/)).skip(string('}'))
  2516. .map(function(text) {
  2517. // TODO: is this the correct behavior when parsing
  2518. // the latex \text{} ? This violates the requirement that
  2519. // the text contents are always nonempty. Should we just
  2520. // disown the parent node instead?
  2521. TextPiece(text).adopt(textBlock, 0, 0);
  2522. return textBlock;
  2523. })
  2524. ;
  2525. };
  2526. _.textContents = function() {
  2527. return this.foldChildren('', function(text, child) {
  2528. return text + child.text;
  2529. });
  2530. };
  2531. _.text = function() { return '"' + this.textContents() + '"'; };
  2532. _.latex = function() { return '\\text{' + this.textContents() + '}'; };
  2533. _.html = function() {
  2534. return (
  2535. '<span class="mq-text-mode" mathquill-command-id='+this.id+'>'
  2536. + this.textContents()
  2537. + '</span>'
  2538. );
  2539. };
  2540. // editability methods: called by the cursor for editing, cursor movements,
  2541. // and selection of the MathQuill tree, these all take in a direction and
  2542. // the cursor
  2543. _.moveTowards = function(dir, cursor) { cursor.insAtDirEnd(-dir, this); };
  2544. _.moveOutOf = function(dir, cursor) { cursor.insDirOf(dir, this); };
  2545. _.unselectInto = _.moveTowards;
  2546. // TODO: make these methods part of a shared mixin or something.
  2547. _.selectTowards = MathCommand.prototype.selectTowards;
  2548. _.deleteTowards = MathCommand.prototype.deleteTowards;
  2549. _.selectOutOf = function(dir, cursor) {
  2550. cursor.insDirOf(dir, this);
  2551. };
  2552. _.deleteOutOf = function(dir, cursor) {
  2553. // backspace and delete at ends of block don't unwrap
  2554. if (this.isEmpty()) cursor.insRightOf(this);
  2555. };
  2556. _.write = function(cursor, ch) {
  2557. cursor.show().deleteSelection();
  2558. if (ch !== '$') {
  2559. if (!cursor[L]) TextPiece(ch).createLeftOf(cursor);
  2560. else cursor[L].appendText(ch);
  2561. }
  2562. else if (this.isEmpty()) {
  2563. cursor.insRightOf(this);
  2564. VanillaSymbol('\\$','$').createLeftOf(cursor);
  2565. }
  2566. else if (!cursor[R]) cursor.insRightOf(this);
  2567. else if (!cursor[L]) cursor.insLeftOf(this);
  2568. else { // split apart
  2569. var leftBlock = TextBlock();
  2570. var leftPc = this.ends[L];
  2571. leftPc.disown();
  2572. leftPc.adopt(leftBlock, 0, 0);
  2573. cursor.insLeftOf(this);
  2574. super_.createLeftOf.call(leftBlock, cursor);
  2575. }
  2576. };
  2577. _.seek = function(pageX, cursor) {
  2578. cursor.hide();
  2579. var textPc = fuseChildren(this);
  2580. // insert cursor at approx position in DOMTextNode
  2581. var avgChWidth = this.jQ.width()/this.text.length;
  2582. var approxPosition = Math.round((pageX - this.jQ.offset().left)/avgChWidth);
  2583. if (approxPosition <= 0) cursor.insAtLeftEnd(this);
  2584. else if (approxPosition >= textPc.text.length) cursor.insAtRightEnd(this);
  2585. else cursor.insLeftOf(textPc.splitRight(approxPosition));
  2586. // move towards mousedown (pageX)
  2587. var displ = pageX - cursor.show().offset().left; // displacement
  2588. var dir = displ && displ < 0 ? L : R;
  2589. var prevDispl = dir;
  2590. // displ * prevDispl > 0 iff displacement direction === previous direction
  2591. while (cursor[dir] && displ * prevDispl > 0) {
  2592. cursor[dir].moveTowards(dir, cursor);
  2593. prevDispl = displ;
  2594. displ = pageX - cursor.offset().left;
  2595. }
  2596. if (dir*displ < -dir*prevDispl) cursor[-dir].moveTowards(-dir, cursor);
  2597. if (!cursor.anticursor) {
  2598. // about to start mouse-selecting, the anticursor is gonna get put here
  2599. this.anticursorPosition = cursor[L] && cursor[L].text.length;
  2600. // ^ get it? 'cos if there's no cursor[L], it's 0... I'm a terrible person.
  2601. }
  2602. else if (cursor.anticursor.parent === this) {
  2603. // mouse-selecting within this TextBlock, re-insert the anticursor
  2604. var cursorPosition = cursor[L] && cursor[L].text.length;;
  2605. if (this.anticursorPosition === cursorPosition) {
  2606. cursor.anticursor = Point.copy(cursor);
  2607. }
  2608. else {
  2609. if (this.anticursorPosition < cursorPosition) {
  2610. var newTextPc = cursor[L].splitRight(this.anticursorPosition);
  2611. cursor[L] = newTextPc;
  2612. }
  2613. else {
  2614. var newTextPc = cursor[R].splitRight(this.anticursorPosition - cursorPosition);
  2615. }
  2616. cursor.anticursor = Point(this, newTextPc[L], newTextPc);
  2617. }
  2618. }
  2619. };
  2620. _.blur = function() {
  2621. MathBlock.prototype.blur.call(this);
  2622. fuseChildren(this);
  2623. };
  2624. function fuseChildren(self) {
  2625. self.jQ[0].normalize();
  2626. var textPcDom = self.jQ[0].firstChild;
  2627. pray('only node in TextBlock span is Text node', textPcDom.nodeType === 3);
  2628. // nodeType === 3 has meant a Text node since ancient times:
  2629. // http://reference.sitepoint.com/javascript/Node/nodeType
  2630. var textPc = TextPiece(textPcDom.data);
  2631. textPc.jQadd(textPcDom);
  2632. self.children().disown();
  2633. return textPc.adopt(self, 0, 0);
  2634. }
  2635. _.focus = MathBlock.prototype.focus;
  2636. });
  2637. /**
  2638. * Piece of plain text, with a TextBlock as a parent and no children.
  2639. * Wraps a single DOMTextNode.
  2640. * For convenience, has a .text property that's just a JavaScript string
  2641. * mirroring the text contents of the DOMTextNode.
  2642. * Text contents must always be nonempty.
  2643. */
  2644. var TextPiece = P(Node, function(_, super_) {
  2645. _.init = function(text) {
  2646. super_.init.call(this);
  2647. this.text = text;
  2648. };
  2649. _.jQadd = function(dom) { this.dom = dom; this.jQ = $(dom); };
  2650. _.jQize = function() {
  2651. return this.jQadd(document.createTextNode(this.text));
  2652. };
  2653. _.appendText = function(text) {
  2654. this.text += text;
  2655. this.dom.appendData(text);
  2656. };
  2657. _.prependText = function(text) {
  2658. this.text = text + this.text;
  2659. this.dom.insertData(0, text);
  2660. };
  2661. _.insTextAtDirEnd = function(text, dir) {
  2662. prayDirection(dir);
  2663. if (dir === R) this.appendText(text);
  2664. else this.prependText(text);
  2665. };
  2666. _.splitRight = function(i) {
  2667. var newPc = TextPiece(this.text.slice(i)).adopt(this.parent, this, this[R]);
  2668. newPc.jQadd(this.dom.splitText(i));
  2669. this.text = this.text.slice(0, i);
  2670. return newPc;
  2671. };
  2672. function endChar(dir, text) {
  2673. return text.charAt(dir === L ? 0 : -1 + text.length);
  2674. }
  2675. _.moveTowards = function(dir, cursor) {
  2676. prayDirection(dir);
  2677. var ch = endChar(-dir, this.text)
  2678. var from = this[-dir];
  2679. if (from) from.insTextAtDirEnd(ch, dir);
  2680. else TextPiece(ch).createDir(-dir, cursor);
  2681. return this.deleteTowards(dir, cursor);
  2682. };
  2683. _.latex = function() { return this.text; };
  2684. _.deleteTowards = function(dir, cursor) {
  2685. if (this.text.length > 1) {
  2686. if (dir === R) {
  2687. this.dom.deleteData(0, 1);
  2688. this.text = this.text.slice(1);
  2689. }
  2690. else {
  2691. // note that the order of these 2 lines is annoyingly important
  2692. // (the second line mutates this.text.length)
  2693. this.dom.deleteData(-1 + this.text.length, 1);
  2694. this.text = this.text.slice(0, -1);
  2695. }
  2696. }
  2697. else {
  2698. this.remove();
  2699. this.jQ.remove();
  2700. cursor[dir] = this[dir];
  2701. }
  2702. };
  2703. _.selectTowards = function(dir, cursor) {
  2704. prayDirection(dir);
  2705. var anticursor = cursor.anticursor;
  2706. var ch = endChar(-dir, this.text)
  2707. if (anticursor[dir] === this) {
  2708. var newPc = TextPiece(ch).createDir(dir, cursor);
  2709. anticursor[dir] = newPc;
  2710. cursor.insDirOf(dir, newPc);
  2711. }
  2712. else {
  2713. var from = this[-dir];
  2714. if (from) from.insTextAtDirEnd(ch, dir);
  2715. else {
  2716. var newPc = TextPiece(ch).createDir(-dir, cursor);
  2717. newPc.jQ.insDirOf(-dir, cursor.selection.jQ);
  2718. }
  2719. if (this.text.length === 1 && anticursor[-dir] === this) {
  2720. anticursor[-dir] = this[-dir]; // `this` will be removed in deleteTowards
  2721. }
  2722. }
  2723. return this.deleteTowards(dir, cursor);
  2724. };
  2725. });
  2726. CharCmds.$ =
  2727. LatexCmds.text =
  2728. LatexCmds.textnormal =
  2729. LatexCmds.textrm =
  2730. LatexCmds.textup =
  2731. LatexCmds.textmd = TextBlock;
  2732. function makeTextBlock(latex, tagName, attrs) {
  2733. return P(TextBlock, {
  2734. ctrlSeq: latex,
  2735. htmlTemplate: '<'+tagName+' '+attrs+'>&0</'+tagName+'>'
  2736. });
  2737. }
  2738. LatexCmds.em = LatexCmds.italic = LatexCmds.italics =
  2739. LatexCmds.emph = LatexCmds.textit = LatexCmds.textsl =
  2740. makeTextBlock('\\textit', 'i', 'class="mq-text-mode"');
  2741. LatexCmds.strong = LatexCmds.bold = LatexCmds.textbf =
  2742. makeTextBlock('\\textbf', 'b', 'class="mq-text-mode"');
  2743. LatexCmds.sf = LatexCmds.textsf =
  2744. makeTextBlock('\\textsf', 'span', 'class="mq-sans-serif mq-text-mode"');
  2745. LatexCmds.tt = LatexCmds.texttt =
  2746. makeTextBlock('\\texttt', 'span', 'class="mq-monospace mq-text-mode"');
  2747. LatexCmds.textsc =
  2748. makeTextBlock('\\textsc', 'span', 'style="font-variant:small-caps" class="mq-text-mode"');
  2749. LatexCmds.uppercase =
  2750. makeTextBlock('\\uppercase', 'span', 'style="text-transform:uppercase" class="mq-text-mode"');
  2751. LatexCmds.lowercase =
  2752. makeTextBlock('\\lowercase', 'span', 'style="text-transform:lowercase" class="mq-text-mode"');
  2753. var RootMathCommand = P(MathCommand, function(_, super_) {
  2754. _.init = function(cursor) {
  2755. super_.init.call(this, '$');
  2756. this.cursor = cursor;
  2757. };
  2758. _.htmlTemplate = '<span class="mq-math-mode">&0</span>';
  2759. _.createBlocks = function() {
  2760. super_.createBlocks.call(this);
  2761. this.ends[L].cursor = this.cursor;
  2762. this.ends[L].write = function(cursor, ch) {
  2763. if (ch !== '$')
  2764. MathBlock.prototype.write.call(this, cursor, ch);
  2765. else if (this.isEmpty()) {
  2766. cursor.insRightOf(this.parent);
  2767. this.parent.deleteTowards(dir, cursor);
  2768. VanillaSymbol('\\$','$').createLeftOf(cursor.show());
  2769. }
  2770. else if (!cursor[R])
  2771. cursor.insRightOf(this.parent);
  2772. else if (!cursor[L])
  2773. cursor.insLeftOf(this.parent);
  2774. else
  2775. MathBlock.prototype.write.call(this, cursor, ch);
  2776. };
  2777. };
  2778. _.latex = function() {
  2779. return '$' + this.ends[L].latex() + '$';
  2780. };
  2781. });
  2782. var RootTextBlock = P(RootMathBlock, function(_, super_) {
  2783. _.keystroke = function(key) {
  2784. if (key === 'Spacebar' || key === 'Shift-Spacebar') return;
  2785. return super_.keystroke.apply(this, arguments);
  2786. };
  2787. _.write = function(cursor, ch) {
  2788. cursor.show().deleteSelection();
  2789. if (ch === '$')
  2790. RootMathCommand(cursor).createLeftOf(cursor);
  2791. else {
  2792. var html;
  2793. if (ch === '<') html = '&lt;';
  2794. else if (ch === '>') html = '&gt;';
  2795. VanillaSymbol(ch, html).createLeftOf(cursor);
  2796. }
  2797. };
  2798. });
  2799. API.TextField = function(APIClasses) {
  2800. return P(APIClasses.EditableField, function(_, super_) {
  2801. this.RootBlock = RootTextBlock;
  2802. _.__mathquillify = function() {
  2803. return super_.__mathquillify.call(this, 'mq-editable-field mq-text-mode');
  2804. };
  2805. _.latex = function(latex) {
  2806. if (arguments.length > 0) {
  2807. this.__controller.renderLatexText(latex);
  2808. if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
  2809. return this;
  2810. }
  2811. return this.__controller.exportLatex();
  2812. };
  2813. });
  2814. };
  2815. /****************************************
  2816. * Input box to type backslash commands
  2817. ***************************************/
  2818. var LatexCommandInput =
  2819. CharCmds['\\'] = P(MathCommand, function(_, super_) {
  2820. _.ctrlSeq = '\\';
  2821. _.replaces = function(replacedFragment) {
  2822. this._replacedFragment = replacedFragment.disown();
  2823. this.isEmpty = function() { return false; };
  2824. };
  2825. _.htmlTemplate = '<span class="mq-latex-command-input mq-non-leaf">\\<span>&0</span></span>';
  2826. _.textTemplate = ['\\'];
  2827. _.createBlocks = function() {
  2828. super_.createBlocks.call(this);
  2829. this.ends[L].focus = function() {
  2830. this.parent.jQ.addClass('mq-hasCursor');
  2831. if (this.isEmpty())
  2832. this.parent.jQ.removeClass('mq-empty');
  2833. return this;
  2834. };
  2835. this.ends[L].blur = function() {
  2836. this.parent.jQ.removeClass('mq-hasCursor');
  2837. if (this.isEmpty())
  2838. this.parent.jQ.addClass('mq-empty');
  2839. return this;
  2840. };
  2841. this.ends[L].write = function(cursor, ch) {
  2842. cursor.show().deleteSelection();
  2843. if (ch.match(/[a-z]/i)) VanillaSymbol(ch).createLeftOf(cursor);
  2844. else {
  2845. this.parent.renderCommand(cursor);
  2846. if (ch !== '\\' || !this.isEmpty()) this.parent.parent.write(cursor, ch);
  2847. }
  2848. };
  2849. this.ends[L].keystroke = function(key, e, ctrlr) {
  2850. if (key === 'Tab' || key === 'Enter' || key === 'Spacebar') {
  2851. this.parent.renderCommand(ctrlr.cursor);
  2852. e.preventDefault();
  2853. return;
  2854. }
  2855. return super_.keystroke.apply(this, arguments);
  2856. };
  2857. };
  2858. _.createLeftOf = function(cursor) {
  2859. super_.createLeftOf.call(this, cursor);
  2860. if (this._replacedFragment) {
  2861. var el = this.jQ[0];
  2862. this.jQ =
  2863. this._replacedFragment.jQ.addClass('mq-blur').bind(
  2864. 'mousedown mousemove', //FIXME: is monkey-patching the mousedown and mousemove handlers the right way to do this?
  2865. function(e) {
  2866. $(e.target = el).trigger(e);
  2867. return false;
  2868. }
  2869. ).insertBefore(this.jQ).add(this.jQ);
  2870. }
  2871. };
  2872. _.latex = function() {
  2873. return '\\' + this.ends[L].latex() + ' ';
  2874. };
  2875. _.renderCommand = function(cursor) {
  2876. this.jQ = this.jQ.last();
  2877. this.remove();
  2878. if (this[R]) {
  2879. cursor.insLeftOf(this[R]);
  2880. } else {
  2881. cursor.insAtRightEnd(this.parent);
  2882. }
  2883. var latex = this.ends[L].latex();
  2884. if (!latex) latex = ' ';
  2885. var cmd = LatexCmds[latex];
  2886. if (cmd) {
  2887. cmd = cmd(latex);
  2888. if (this._replacedFragment) cmd.replaces(this._replacedFragment);
  2889. cmd.createLeftOf(cursor);
  2890. }
  2891. else {
  2892. cmd = TextBlock();
  2893. cmd.replaces(latex);
  2894. cmd.createLeftOf(cursor);
  2895. cursor.insRightOf(cmd);
  2896. if (this._replacedFragment)
  2897. this._replacedFragment.remove();
  2898. }
  2899. };
  2900. });
  2901. /************************************
  2902. * Symbols for Advanced Mathematics
  2903. ***********************************/
  2904. LatexCmds.notin =
  2905. LatexCmds.cong =
  2906. LatexCmds.equiv =
  2907. LatexCmds.oplus =
  2908. LatexCmds.otimes = P(BinaryOperator, function(_, super_) {
  2909. _.init = function(latex) {
  2910. super_.init.call(this, '\\'+latex+' ', '&'+latex+';');
  2911. };
  2912. });
  2913. LatexCmds['≠'] = LatexCmds.ne = LatexCmds.neq = bind(BinaryOperator,'\\ne ','&ne;');
  2914. LatexCmds.ast = LatexCmds.star = LatexCmds.loast = LatexCmds.lowast =
  2915. bind(BinaryOperator,'\\ast ','&lowast;');
  2916. //case 'there4 = // a special exception for this one, perhaps?
  2917. LatexCmds.therefor = LatexCmds.therefore =
  2918. bind(BinaryOperator,'\\therefore ','&there4;');
  2919. LatexCmds.cuz = // l33t
  2920. LatexCmds.because = bind(BinaryOperator,'\\because ','&#8757;');
  2921. LatexCmds.prop = LatexCmds.propto = bind(BinaryOperator,'\\propto ','&prop;');
  2922. LatexCmds['≈'] = LatexCmds.asymp = LatexCmds.approx = bind(BinaryOperator,'\\approx ','&asymp;');
  2923. LatexCmds.isin = LatexCmds['in'] = bind(BinaryOperator,'\\in ','&isin;');
  2924. LatexCmds.ni = LatexCmds.contains = bind(BinaryOperator,'\\ni ','&ni;');
  2925. LatexCmds.notni = LatexCmds.niton = LatexCmds.notcontains = LatexCmds.doesnotcontain =
  2926. bind(BinaryOperator,'\\not\\ni ','&#8716;');
  2927. LatexCmds.sub = LatexCmds.subset = bind(BinaryOperator,'\\subset ','&sub;');
  2928. LatexCmds.sup = LatexCmds.supset = LatexCmds.superset =
  2929. bind(BinaryOperator,'\\supset ','&sup;');
  2930. LatexCmds.nsub = LatexCmds.notsub =
  2931. LatexCmds.nsubset = LatexCmds.notsubset =
  2932. bind(BinaryOperator,'\\not\\subset ','&#8836;');
  2933. LatexCmds.nsup = LatexCmds.notsup =
  2934. LatexCmds.nsupset = LatexCmds.notsupset =
  2935. LatexCmds.nsuperset = LatexCmds.notsuperset =
  2936. bind(BinaryOperator,'\\not\\supset ','&#8837;');
  2937. LatexCmds.sube = LatexCmds.subeq = LatexCmds.subsete = LatexCmds.subseteq =
  2938. bind(BinaryOperator,'\\subseteq ','&sube;');
  2939. LatexCmds.supe = LatexCmds.supeq =
  2940. LatexCmds.supsete = LatexCmds.supseteq =
  2941. LatexCmds.supersete = LatexCmds.superseteq =
  2942. bind(BinaryOperator,'\\supseteq ','&supe;');
  2943. LatexCmds.nsube = LatexCmds.nsubeq =
  2944. LatexCmds.notsube = LatexCmds.notsubeq =
  2945. LatexCmds.nsubsete = LatexCmds.nsubseteq =
  2946. LatexCmds.notsubsete = LatexCmds.notsubseteq =
  2947. bind(BinaryOperator,'\\not\\subseteq ','&#8840;');
  2948. LatexCmds.nsupe = LatexCmds.nsupeq =
  2949. LatexCmds.notsupe = LatexCmds.notsupeq =
  2950. LatexCmds.nsupsete = LatexCmds.nsupseteq =
  2951. LatexCmds.notsupsete = LatexCmds.notsupseteq =
  2952. LatexCmds.nsupersete = LatexCmds.nsuperseteq =
  2953. LatexCmds.notsupersete = LatexCmds.notsuperseteq =
  2954. bind(BinaryOperator,'\\not\\supseteq ','&#8841;');
  2955. //the canonical sets of numbers
  2956. LatexCmds.N = LatexCmds.naturals = LatexCmds.Naturals =
  2957. bind(VanillaSymbol,'\\mathbb{N}','&#8469;');
  2958. LatexCmds.P =
  2959. LatexCmds.primes = LatexCmds.Primes =
  2960. LatexCmds.projective = LatexCmds.Projective =
  2961. LatexCmds.probability = LatexCmds.Probability =
  2962. bind(VanillaSymbol,'\\mathbb{P}','&#8473;');
  2963. LatexCmds.Z = LatexCmds.integers = LatexCmds.Integers =
  2964. bind(VanillaSymbol,'\\mathbb{Z}','&#8484;');
  2965. LatexCmds.Q = LatexCmds.rationals = LatexCmds.Rationals =
  2966. bind(VanillaSymbol,'\\mathbb{Q}','&#8474;');
  2967. LatexCmds.R = LatexCmds.reals = LatexCmds.Reals =
  2968. bind(VanillaSymbol,'\\mathbb{R}','&#8477;');
  2969. LatexCmds.C =
  2970. LatexCmds.complex = LatexCmds.Complex =
  2971. LatexCmds.complexes = LatexCmds.Complexes =
  2972. LatexCmds.complexplane = LatexCmds.Complexplane = LatexCmds.ComplexPlane =
  2973. bind(VanillaSymbol,'\\mathbb{C}','&#8450;');
  2974. LatexCmds.H = LatexCmds.Hamiltonian = LatexCmds.quaternions = LatexCmds.Quaternions =
  2975. bind(VanillaSymbol,'\\mathbb{H}','&#8461;');
  2976. //spacing
  2977. LatexCmds.quad = LatexCmds.emsp = bind(VanillaSymbol,'\\quad ',' ');
  2978. LatexCmds.qquad = bind(VanillaSymbol,'\\qquad ',' ');
  2979. /* spacing special characters, gonna have to implement this in LatexCommandInput::onText somehow
  2980. case ',':
  2981. return VanillaSymbol('\\, ',' ');
  2982. case ':':
  2983. return VanillaSymbol('\\: ',' ');
  2984. case ';':
  2985. return VanillaSymbol('\\; ',' ');
  2986. case '!':
  2987. return Symbol('\\! ','<span style="margin-right:-.2em"></span>');
  2988. */
  2989. //binary operators
  2990. LatexCmds.diamond = bind(VanillaSymbol, '\\diamond ', '&#9671;');
  2991. LatexCmds.bigtriangleup = bind(VanillaSymbol, '\\bigtriangleup ', '&#9651;');
  2992. LatexCmds.ominus = bind(VanillaSymbol, '\\ominus ', '&#8854;');
  2993. LatexCmds.uplus = bind(VanillaSymbol, '\\uplus ', '&#8846;');
  2994. LatexCmds.bigtriangledown = bind(VanillaSymbol, '\\bigtriangledown ', '&#9661;');
  2995. LatexCmds.sqcap = bind(VanillaSymbol, '\\sqcap ', '&#8851;');
  2996. LatexCmds.triangleleft = bind(VanillaSymbol, '\\triangleleft ', '&#8882;');
  2997. LatexCmds.sqcup = bind(VanillaSymbol, '\\sqcup ', '&#8852;');
  2998. LatexCmds.triangleright = bind(VanillaSymbol, '\\triangleright ', '&#8883;');
  2999. //circledot is not a not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
  3000. LatexCmds.odot = LatexCmds.circledot = bind(VanillaSymbol, '\\odot ', '&#8857;');
  3001. LatexCmds.bigcirc = bind(VanillaSymbol, '\\bigcirc ', '&#9711;');
  3002. LatexCmds.dagger = bind(VanillaSymbol, '\\dagger ', '&#0134;');
  3003. LatexCmds.ddagger = bind(VanillaSymbol, '\\ddagger ', '&#135;');
  3004. LatexCmds.wr = bind(VanillaSymbol, '\\wr ', '&#8768;');
  3005. LatexCmds.amalg = bind(VanillaSymbol, '\\amalg ', '&#8720;');
  3006. //relationship symbols
  3007. LatexCmds.models = bind(VanillaSymbol, '\\models ', '&#8872;');
  3008. LatexCmds.prec = bind(VanillaSymbol, '\\prec ', '&#8826;');
  3009. LatexCmds.succ = bind(VanillaSymbol, '\\succ ', '&#8827;');
  3010. LatexCmds.preceq = bind(VanillaSymbol, '\\preceq ', '&#8828;');
  3011. LatexCmds.succeq = bind(VanillaSymbol, '\\succeq ', '&#8829;');
  3012. LatexCmds.simeq = bind(VanillaSymbol, '\\simeq ', '&#8771;');
  3013. LatexCmds.mid = bind(VanillaSymbol, '\\mid ', '&#8739;');
  3014. LatexCmds.ll = bind(VanillaSymbol, '\\ll ', '&#8810;');
  3015. LatexCmds.gg = bind(VanillaSymbol, '\\gg ', '&#8811;');
  3016. LatexCmds.parallel = bind(VanillaSymbol, '\\parallel ', '&#8741;');
  3017. LatexCmds.nparallel = bind(VanillaSymbol, '\\nparallel ', '&#8742;');
  3018. LatexCmds.bowtie = bind(VanillaSymbol, '\\bowtie ', '&#8904;');
  3019. LatexCmds.sqsubset = bind(VanillaSymbol, '\\sqsubset ', '&#8847;');
  3020. LatexCmds.sqsupset = bind(VanillaSymbol, '\\sqsupset ', '&#8848;');
  3021. LatexCmds.smile = bind(VanillaSymbol, '\\smile ', '&#8995;');
  3022. LatexCmds.sqsubseteq = bind(VanillaSymbol, '\\sqsubseteq ', '&#8849;');
  3023. LatexCmds.sqsupseteq = bind(VanillaSymbol, '\\sqsupseteq ', '&#8850;');
  3024. LatexCmds.doteq = bind(VanillaSymbol, '\\doteq ', '&#8784;');
  3025. LatexCmds.frown = bind(VanillaSymbol, '\\frown ', '&#8994;');
  3026. LatexCmds.vdash = bind(VanillaSymbol, '\\vdash ', '&#8870;');
  3027. LatexCmds.dashv = bind(VanillaSymbol, '\\dashv ', '&#8867;');
  3028. LatexCmds.nless = bind(VanillaSymbol, '\\nless ', '&#8814;');
  3029. LatexCmds.ngtr = bind(VanillaSymbol, '\\ngtr ', '&#8815;');
  3030. //arrows
  3031. LatexCmds.longleftarrow = bind(VanillaSymbol, '\\longleftarrow ', '&#8592;');
  3032. LatexCmds.longrightarrow = bind(VanillaSymbol, '\\longrightarrow ', '&#8594;');
  3033. LatexCmds.Longleftarrow = bind(VanillaSymbol, '\\Longleftarrow ', '&#8656;');
  3034. LatexCmds.Longrightarrow = bind(VanillaSymbol, '\\Longrightarrow ', '&#8658;');
  3035. LatexCmds.longleftrightarrow = bind(VanillaSymbol, '\\longleftrightarrow ', '&#8596;');
  3036. LatexCmds.updownarrow = bind(VanillaSymbol, '\\updownarrow ', '&#8597;');
  3037. LatexCmds.Longleftrightarrow = bind(VanillaSymbol, '\\Longleftrightarrow ', '&#8660;');
  3038. LatexCmds.Updownarrow = bind(VanillaSymbol, '\\Updownarrow ', '&#8661;');
  3039. LatexCmds.mapsto = bind(VanillaSymbol, '\\mapsto ', '&#8614;');
  3040. LatexCmds.nearrow = bind(VanillaSymbol, '\\nearrow ', '&#8599;');
  3041. LatexCmds.hookleftarrow = bind(VanillaSymbol, '\\hookleftarrow ', '&#8617;');
  3042. LatexCmds.hookrightarrow = bind(VanillaSymbol, '\\hookrightarrow ', '&#8618;');
  3043. LatexCmds.searrow = bind(VanillaSymbol, '\\searrow ', '&#8600;');
  3044. LatexCmds.leftharpoonup = bind(VanillaSymbol, '\\leftharpoonup ', '&#8636;');
  3045. LatexCmds.rightharpoonup = bind(VanillaSymbol, '\\rightharpoonup ', '&#8640;');
  3046. LatexCmds.swarrow = bind(VanillaSymbol, '\\swarrow ', '&#8601;');
  3047. LatexCmds.leftharpoondown = bind(VanillaSymbol, '\\leftharpoondown ', '&#8637;');
  3048. LatexCmds.rightharpoondown = bind(VanillaSymbol, '\\rightharpoondown ', '&#8641;');
  3049. LatexCmds.nwarrow = bind(VanillaSymbol, '\\nwarrow ', '&#8598;');
  3050. //Misc
  3051. LatexCmds.ldots = bind(VanillaSymbol, '\\ldots ', '&#8230;');
  3052. LatexCmds.cdots = bind(VanillaSymbol, '\\cdots ', '&#8943;');
  3053. LatexCmds.vdots = bind(VanillaSymbol, '\\vdots ', '&#8942;');
  3054. LatexCmds.ddots = bind(VanillaSymbol, '\\ddots ', '&#8945;');
  3055. LatexCmds.surd = bind(VanillaSymbol, '\\surd ', '&#8730;');
  3056. LatexCmds.triangle = bind(VanillaSymbol, '\\triangle ', '&#9651;');
  3057. LatexCmds.ell = bind(VanillaSymbol, '\\ell ', '&#8467;');
  3058. LatexCmds.top = bind(VanillaSymbol, '\\top ', '&#8868;');
  3059. LatexCmds.flat = bind(VanillaSymbol, '\\flat ', '&#9837;');
  3060. LatexCmds.natural = bind(VanillaSymbol, '\\natural ', '&#9838;');
  3061. LatexCmds.sharp = bind(VanillaSymbol, '\\sharp ', '&#9839;');
  3062. LatexCmds.wp = bind(VanillaSymbol, '\\wp ', '&#8472;');
  3063. LatexCmds.bot = bind(VanillaSymbol, '\\bot ', '&#8869;');
  3064. LatexCmds.clubsuit = bind(VanillaSymbol, '\\clubsuit ', '&#9827;');
  3065. LatexCmds.diamondsuit = bind(VanillaSymbol, '\\diamondsuit ', '&#9826;');
  3066. LatexCmds.heartsuit = bind(VanillaSymbol, '\\heartsuit ', '&#9825;');
  3067. LatexCmds.spadesuit = bind(VanillaSymbol, '\\spadesuit ', '&#9824;');
  3068. //not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
  3069. LatexCmds.parallelogram = bind(VanillaSymbol, '\\parallelogram ', '&#9649;');
  3070. LatexCmds.square = bind(VanillaSymbol, '\\square ', '&#11036;');
  3071. //variable-sized
  3072. LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '&#8750;');
  3073. LatexCmds.bigcap = bind(VanillaSymbol, '\\bigcap ', '&#8745;');
  3074. LatexCmds.bigcup = bind(VanillaSymbol, '\\bigcup ', '&#8746;');
  3075. LatexCmds.bigsqcup = bind(VanillaSymbol, '\\bigsqcup ', '&#8852;');
  3076. LatexCmds.bigvee = bind(VanillaSymbol, '\\bigvee ', '&#8744;');
  3077. LatexCmds.bigwedge = bind(VanillaSymbol, '\\bigwedge ', '&#8743;');
  3078. LatexCmds.bigodot = bind(VanillaSymbol, '\\bigodot ', '&#8857;');
  3079. LatexCmds.bigotimes = bind(VanillaSymbol, '\\bigotimes ', '&#8855;');
  3080. LatexCmds.bigoplus = bind(VanillaSymbol, '\\bigoplus ', '&#8853;');
  3081. LatexCmds.biguplus = bind(VanillaSymbol, '\\biguplus ', '&#8846;');
  3082. //delimiters
  3083. LatexCmds.lfloor = bind(VanillaSymbol, '\\lfloor ', '&#8970;');
  3084. LatexCmds.rfloor = bind(VanillaSymbol, '\\rfloor ', '&#8971;');
  3085. LatexCmds.lceil = bind(VanillaSymbol, '\\lceil ', '&#8968;');
  3086. LatexCmds.rceil = bind(VanillaSymbol, '\\rceil ', '&#8969;');
  3087. LatexCmds.opencurlybrace = LatexCmds.lbrace = bind(VanillaSymbol, '\\lbrace ', '{');
  3088. LatexCmds.closecurlybrace = LatexCmds.rbrace = bind(VanillaSymbol, '\\rbrace ', '}');
  3089. LatexCmds.lbrack = bind(VanillaSymbol, '[');
  3090. LatexCmds.rbrack = bind(VanillaSymbol, ']');
  3091. //various symbols
  3092. LatexCmds['∫'] =
  3093. LatexCmds['int'] =
  3094. LatexCmds.integral = bind(Symbol,'\\int ','<big>&int;</big>');
  3095. LatexCmds.slash = bind(VanillaSymbol, '/');
  3096. LatexCmds.vert = bind(VanillaSymbol,'|');
  3097. LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','&perp;');
  3098. LatexCmds.nabla = LatexCmds.del = bind(VanillaSymbol,'\\nabla ','&nabla;');
  3099. LatexCmds.hbar = bind(VanillaSymbol,'\\hbar ','&#8463;');
  3100. LatexCmds.AA = LatexCmds.Angstrom = LatexCmds.angstrom =
  3101. bind(VanillaSymbol,'\\text\\AA ','&#8491;');
  3102. LatexCmds.ring = LatexCmds.circ = LatexCmds.circle =
  3103. bind(VanillaSymbol,'\\circ ','&#8728;');
  3104. LatexCmds.bull = LatexCmds.bullet = bind(VanillaSymbol,'\\bullet ','&bull;');
  3105. LatexCmds.setminus = LatexCmds.smallsetminus =
  3106. bind(VanillaSymbol,'\\setminus ','&#8726;');
  3107. LatexCmds.not = //bind(Symbol,'\\not ','<span class="not">/</span>');
  3108. LatexCmds['¬'] = LatexCmds.neg = bind(VanillaSymbol,'\\neg ','&not;');
  3109. LatexCmds['…'] = LatexCmds.dots = LatexCmds.ellip = LatexCmds.hellip =
  3110. LatexCmds.ellipsis = LatexCmds.hellipsis =
  3111. bind(VanillaSymbol,'\\dots ','&hellip;');
  3112. LatexCmds.converges =
  3113. LatexCmds.darr = LatexCmds.dnarr = LatexCmds.dnarrow = LatexCmds.downarrow =
  3114. bind(VanillaSymbol,'\\downarrow ','&darr;');
  3115. LatexCmds.dArr = LatexCmds.dnArr = LatexCmds.dnArrow = LatexCmds.Downarrow =
  3116. bind(VanillaSymbol,'\\Downarrow ','&dArr;');
  3117. LatexCmds.diverges = LatexCmds.uarr = LatexCmds.uparrow =
  3118. bind(VanillaSymbol,'\\uparrow ','&uarr;');
  3119. LatexCmds.uArr = LatexCmds.Uparrow = bind(VanillaSymbol,'\\Uparrow ','&uArr;');
  3120. LatexCmds.to = bind(BinaryOperator,'\\to ','&rarr;');
  3121. LatexCmds.rarr = LatexCmds.rightarrow = bind(VanillaSymbol,'\\rightarrow ','&rarr;');
  3122. LatexCmds.implies = bind(BinaryOperator,'\\Rightarrow ','&rArr;');
  3123. LatexCmds.rArr = LatexCmds.Rightarrow = bind(VanillaSymbol,'\\Rightarrow ','&rArr;');
  3124. LatexCmds.gets = bind(BinaryOperator,'\\gets ','&larr;');
  3125. LatexCmds.larr = LatexCmds.leftarrow = bind(VanillaSymbol,'\\leftarrow ','&larr;');
  3126. LatexCmds.impliedby = bind(BinaryOperator,'\\Leftarrow ','&lArr;');
  3127. LatexCmds.lArr = LatexCmds.Leftarrow = bind(VanillaSymbol,'\\Leftarrow ','&lArr;');
  3128. LatexCmds.harr = LatexCmds.lrarr = LatexCmds.leftrightarrow =
  3129. bind(VanillaSymbol,'\\leftrightarrow ','&harr;');
  3130. LatexCmds.iff = bind(BinaryOperator,'\\Leftrightarrow ','&hArr;');
  3131. LatexCmds.hArr = LatexCmds.lrArr = LatexCmds.Leftrightarrow =
  3132. bind(VanillaSymbol,'\\Leftrightarrow ','&hArr;');
  3133. LatexCmds.Re = LatexCmds.Real = LatexCmds.real = bind(VanillaSymbol,'\\Re ','&real;');
  3134. LatexCmds.Im = LatexCmds.imag =
  3135. LatexCmds.image = LatexCmds.imagin = LatexCmds.imaginary = LatexCmds.Imaginary =
  3136. bind(VanillaSymbol,'\\Im ','&image;');
  3137. LatexCmds.part = LatexCmds.partial = bind(VanillaSymbol,'\\partial ','&part;');
  3138. LatexCmds.infty = LatexCmds.infin = LatexCmds.infinity =
  3139. bind(VanillaSymbol,'\\infty ','&infin;');
  3140. LatexCmds.alef = LatexCmds.alefsym = LatexCmds.aleph = LatexCmds.alephsym =
  3141. bind(VanillaSymbol,'\\aleph ','&alefsym;');
  3142. LatexCmds.xist = //LOL
  3143. LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
  3144. bind(VanillaSymbol,'\\exists ','&exist;');
  3145. LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
  3146. bind(VanillaSymbol,'\\wedge ','&and;');
  3147. LatexCmds.or = LatexCmds.lor = LatexCmds.vee = bind(VanillaSymbol,'\\vee ','&or;');
  3148. LatexCmds.o = LatexCmds.O =
  3149. LatexCmds.empty = LatexCmds.emptyset =
  3150. LatexCmds.oslash = LatexCmds.Oslash =
  3151. LatexCmds.nothing = LatexCmds.varnothing =
  3152. bind(BinaryOperator,'\\varnothing ','&empty;');
  3153. LatexCmds.cup = LatexCmds.union = bind(BinaryOperator,'\\cup ','&cup;');
  3154. LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection =
  3155. bind(BinaryOperator,'\\cap ','&cap;');
  3156. // FIXME: the correct LaTeX would be ^\circ but we can't parse that
  3157. LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'\\degree ','&deg;');
  3158. LatexCmds.ang = LatexCmds.angle = bind(VanillaSymbol,'\\angle ','&ang;');
  3159. LatexCmds.measuredangle = bind(VanillaSymbol,'\\measuredangle ','&#8737;');
  3160. /*********************************
  3161. * Symbols for Basic Mathematics
  3162. ********************************/
  3163. var Digit = P(VanillaSymbol, function(_, super_) {
  3164. _.createLeftOf = function(cursor) {
  3165. if (cursor.options.autoSubscriptNumerals
  3166. && cursor.parent !== cursor.parent.parent.sub
  3167. && ((cursor[L] instanceof Variable && cursor[L].isItalic !== false)
  3168. || (cursor[L] instanceof SupSub
  3169. && cursor[L][L] instanceof Variable
  3170. && cursor[L][L].isItalic !== false))) {
  3171. LatexCmds._().createLeftOf(cursor);
  3172. super_.createLeftOf.call(this, cursor);
  3173. cursor.insRightOf(cursor.parent.parent);
  3174. }
  3175. else super_.createLeftOf.call(this, cursor);
  3176. };
  3177. });
  3178. var Variable = P(Symbol, function(_, super_) {
  3179. _.init = function(ch, html) {
  3180. super_.init.call(this, ch, '<var>'+(html || ch)+'</var>');
  3181. };
  3182. _.text = function() {
  3183. var text = this.ctrlSeq;
  3184. if (this[L] && !(this[L] instanceof Variable)
  3185. && !(this[L] instanceof BinaryOperator)
  3186. && this[L].ctrlSeq !== "\\ ")
  3187. text = '*' + text;
  3188. if (this[R] && !(this[R] instanceof BinaryOperator)
  3189. && !(this[R] instanceof SupSub))
  3190. text += '*';
  3191. return text;
  3192. };
  3193. });
  3194. Options.p.autoCommands = { _maxLength: 0 };
  3195. optionProcessors.autoCommands = function(cmds) {
  3196. if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
  3197. throw '"'+cmds+'" not a space-delimited list of only letters';
  3198. }
  3199. var list = cmds.split(' '), dict = {}, maxLength = 0;
  3200. for (var i = 0; i < list.length; i += 1) {
  3201. var cmd = list[i];
  3202. if (cmd.length < 2) {
  3203. throw 'autocommand "'+cmd+'" not minimum length of 2';
  3204. }
  3205. if (LatexCmds[cmd] === OperatorName) {
  3206. throw '"' + cmd + '" is a built-in operator name';
  3207. }
  3208. dict[cmd] = 1;
  3209. maxLength = max(maxLength, cmd.length);
  3210. }
  3211. dict._maxLength = maxLength;
  3212. return dict;
  3213. };
  3214. var Letter = P(Variable, function(_, super_) {
  3215. _.init = function(ch) { return super_.init.call(this, this.letter = ch); };
  3216. _.createLeftOf = function(cursor) {
  3217. var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
  3218. if (maxLength > 0) {
  3219. // want longest possible autocommand, so join together longest
  3220. // sequence of letters
  3221. var str = this.letter, l = cursor[L], i = 1;
  3222. while (l instanceof Letter && i < maxLength) {
  3223. str = l.letter + str, l = l[L], i += 1;
  3224. }
  3225. // check for an autocommand, going thru substrings longest to shortest
  3226. while (str.length) {
  3227. if (autoCmds.hasOwnProperty(str)) {
  3228. for (var i = 2, l = cursor[L]; i < str.length; i += 1, l = l[L]);
  3229. Fragment(l, cursor[L]).remove();
  3230. cursor[L] = l[L];
  3231. return LatexCmds[str](str).createLeftOf(cursor);
  3232. }
  3233. str = str.slice(1);
  3234. }
  3235. }
  3236. super_.createLeftOf.apply(this, arguments);
  3237. };
  3238. _.italicize = function(bool) {
  3239. this.isItalic = bool;
  3240. this.jQ.toggleClass('mq-operator-name', !bool);
  3241. return this;
  3242. };
  3243. _.finalizeTree = _.siblingDeleted = _.siblingCreated = function(opts, dir) {
  3244. // don't auto-un-italicize if the sibling to my right changed (dir === R or
  3245. // undefined) and it's now a Letter, it will un-italicize everyone
  3246. if (dir !== L && this[R] instanceof Letter) return;
  3247. this.autoUnItalicize(opts);
  3248. };
  3249. _.autoUnItalicize = function(opts) {
  3250. var autoOps = opts.autoOperatorNames;
  3251. if (autoOps._maxLength === 0) return;
  3252. // want longest possible operator names, so join together entire contiguous
  3253. // sequence of letters
  3254. var str = this.letter;
  3255. for (var l = this[L]; l instanceof Letter; l = l[L]) str = l.letter + str;
  3256. for (var r = this[R]; r instanceof Letter; r = r[R]) str += r.letter;
  3257. // removeClass and delete flags from all letters before figuring out
  3258. // which, if any, are part of an operator name
  3259. Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
  3260. el.italicize(true).jQ.removeClass('mq-first mq-last');
  3261. el.ctrlSeq = el.letter;
  3262. });
  3263. // check for operator names: at each position from left to right, check
  3264. // substrings from longest to shortest
  3265. outer: for (var i = 0, first = l[R] || this.parent.ends[L]; i < str.length; i += 1, first = first[R]) {
  3266. for (var len = min(autoOps._maxLength, str.length - i); len > 0; len -= 1) {
  3267. var word = str.slice(i, i + len);
  3268. if (autoOps.hasOwnProperty(word)) {
  3269. for (var j = 0, letter = first; j < len; j += 1, letter = letter[R]) {
  3270. letter.italicize(false);
  3271. var last = letter;
  3272. }
  3273. var isBuiltIn = BuiltInOpNames.hasOwnProperty(word);
  3274. first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
  3275. last.ctrlSeq += (isBuiltIn ? ' ' : '}');
  3276. if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
  3277. if (nonOperatorSymbol(first[L])) first.jQ.addClass('mq-first');
  3278. if (nonOperatorSymbol(last[R])) last.jQ.addClass('mq-last');
  3279. i += len - 1;
  3280. first = last;
  3281. continue outer;
  3282. }
  3283. }
  3284. }
  3285. };
  3286. function nonOperatorSymbol(node) {
  3287. return node instanceof Symbol && !(node instanceof BinaryOperator);
  3288. }
  3289. });
  3290. var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
  3291. // are built-into LaTeX: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
  3292. // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
  3293. // and 'arsinh', which must be exported as \operatorname{hcf} and
  3294. // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
  3295. // \varlimsup are not supported
  3296. var AutoOpNames = Options.p.autoOperatorNames = { _maxLength: 9 }; // the set
  3297. // of operator names that MathQuill auto-unitalicizes by default; overridable
  3298. var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 };
  3299. (function() {
  3300. var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup'
  3301. + ' limsup liminf injlim projlim Pr').split(' ');
  3302. for (var i = 0; i < mostOps.length; i += 1) {
  3303. BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1;
  3304. }
  3305. var builtInTrigs = // why coth but not sech and csch, LaTeX?
  3306. 'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' ');
  3307. for (var i = 0; i < builtInTrigs.length; i += 1) {
  3308. BuiltInOpNames[builtInTrigs[i]] = 1;
  3309. }
  3310. var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' ');
  3311. for (var i = 0; i < autoTrigs.length; i += 1) {
  3312. AutoOpNames[autoTrigs[i]] =
  3313. AutoOpNames['arc'+autoTrigs[i]] =
  3314. AutoOpNames[autoTrigs[i]+'h'] =
  3315. AutoOpNames['ar'+autoTrigs[i]+'h'] =
  3316. AutoOpNames['arc'+autoTrigs[i]+'h'] = 1;
  3317. }
  3318. // compat with some of the nonstandard LaTeX exported by MathQuill
  3319. // before #247. None of these are real LaTeX commands so, seems safe
  3320. var moreNonstandardOps = 'gcf hcf lcm proj span'.split(' ');
  3321. for (var i = 0; i < moreNonstandardOps.length; i += 1) {
  3322. AutoOpNames[moreNonstandardOps[i]] = 1;
  3323. }
  3324. }());
  3325. optionProcessors.autoOperatorNames = function(cmds) {
  3326. if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
  3327. throw '"'+cmds+'" not a space-delimited list of only letters';
  3328. }
  3329. var list = cmds.split(' '), dict = {}, maxLength = 0;
  3330. for (var i = 0; i < list.length; i += 1) {
  3331. var cmd = list[i];
  3332. if (cmd.length < 2) {
  3333. throw '"'+cmd+'" not minimum length of 2';
  3334. }
  3335. dict[cmd] = 1;
  3336. maxLength = max(maxLength, cmd.length);
  3337. }
  3338. dict._maxLength = maxLength;
  3339. return dict;
  3340. };
  3341. var OperatorName = P(Symbol, function(_, super_) {
  3342. _.init = function(fn) { this.ctrlSeq = fn; };
  3343. _.createLeftOf = function(cursor) {
  3344. var fn = this.ctrlSeq;
  3345. for (var i = 0; i < fn.length; i += 1) {
  3346. Letter(fn.charAt(i)).createLeftOf(cursor);
  3347. }
  3348. };
  3349. _.parser = function() {
  3350. var fn = this.ctrlSeq;
  3351. var block = MathBlock();
  3352. for (var i = 0; i < fn.length; i += 1) {
  3353. Letter(fn.charAt(i)).adopt(block, block.ends[R], 0);
  3354. }
  3355. return Parser.succeed(block.children());
  3356. };
  3357. });
  3358. for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) {
  3359. LatexCmds[fn] = OperatorName;
  3360. }
  3361. LatexCmds.operatorname = P(MathCommand, function(_) {
  3362. _.createLeftOf = noop;
  3363. _.numBlocks = function() { return 1; };
  3364. _.parser = function() {
  3365. return latexMathParser.block.map(function(b) { return b.children(); });
  3366. };
  3367. });
  3368. LatexCmds.f = P(Letter, function(_, super_) {
  3369. _.init = function() {
  3370. Symbol.p.init.call(this, this.letter = 'f', '<var class="mq-f">f</var>');
  3371. };
  3372. _.italicize = function(bool) {
  3373. this.jQ.html('f').toggleClass('mq-f', bool);
  3374. return super_.italicize.apply(this, arguments);
  3375. };
  3376. });
  3377. // VanillaSymbol's
  3378. LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', '&nbsp;');
  3379. LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '&prime;');
  3380. LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
  3381. if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash;
  3382. LatexCmds.$ = bind(VanillaSymbol, '\\$', '$');
  3383. // does not use Symbola font
  3384. var NonSymbolaSymbol = P(Symbol, function(_, super_) {
  3385. _.init = function(ch, html) {
  3386. super_.init.call(this, ch, '<span class="mq-nonSymbola">'+(html || ch)+'</span>');
  3387. };
  3388. });
  3389. LatexCmds['@'] = NonSymbolaSymbol;
  3390. LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&amp;');
  3391. LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
  3392. //the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html
  3393. //lowercase Greek letter variables
  3394. LatexCmds.alpha =
  3395. LatexCmds.beta =
  3396. LatexCmds.gamma =
  3397. LatexCmds.delta =
  3398. LatexCmds.zeta =
  3399. LatexCmds.eta =
  3400. LatexCmds.theta =
  3401. LatexCmds.iota =
  3402. LatexCmds.kappa =
  3403. LatexCmds.mu =
  3404. LatexCmds.nu =
  3405. LatexCmds.xi =
  3406. LatexCmds.rho =
  3407. LatexCmds.sigma =
  3408. LatexCmds.tau =
  3409. LatexCmds.chi =
  3410. LatexCmds.psi =
  3411. LatexCmds.omega = P(Variable, function(_, super_) {
  3412. _.init = function(latex) {
  3413. super_.init.call(this,'\\'+latex+' ','&'+latex+';');
  3414. };
  3415. });
  3416. //why can't anybody FUCKING agree on these
  3417. LatexCmds.phi = //W3C or Unicode?
  3418. bind(Variable,'\\phi ','&#981;');
  3419. LatexCmds.phiv = //Elsevier and 9573-13
  3420. LatexCmds.varphi = //AMS and LaTeX
  3421. bind(Variable,'\\varphi ','&phi;');
  3422. LatexCmds.epsilon = //W3C or Unicode?
  3423. bind(Variable,'\\epsilon ','&#1013;');
  3424. LatexCmds.epsiv = //Elsevier and 9573-13
  3425. LatexCmds.varepsilon = //AMS and LaTeX
  3426. bind(Variable,'\\varepsilon ','&epsilon;');
  3427. LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13
  3428. LatexCmds.varpi = //AMS and LaTeX
  3429. bind(Variable,'\\varpi ','&piv;');
  3430. LatexCmds.sigmaf = //W3C/Unicode
  3431. LatexCmds.sigmav = //Elsevier
  3432. LatexCmds.varsigma = //LaTeX
  3433. bind(Variable,'\\varsigma ','&sigmaf;');
  3434. LatexCmds.thetav = //Elsevier and 9573-13
  3435. LatexCmds.vartheta = //AMS and LaTeX
  3436. LatexCmds.thetasym = //W3C/Unicode
  3437. bind(Variable,'\\vartheta ','&thetasym;');
  3438. LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode
  3439. LatexCmds.upsi = //Elsevier and 9573-13
  3440. bind(Variable,'\\upsilon ','&upsilon;');
  3441. //these aren't even mentioned in the HTML character entity references
  3442. LatexCmds.gammad = //Elsevier
  3443. LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above)
  3444. LatexCmds.digamma = //LaTeX
  3445. bind(Variable,'\\digamma ','&#989;');
  3446. LatexCmds.kappav = //Elsevier
  3447. LatexCmds.varkappa = //AMS and LaTeX
  3448. bind(Variable,'\\varkappa ','&#1008;');
  3449. LatexCmds.rhov = //Elsevier and 9573-13
  3450. LatexCmds.varrho = //AMS and LaTeX
  3451. bind(Variable,'\\varrho ','&#1009;');
  3452. //Greek constants, look best in non-italicized Times New Roman
  3453. LatexCmds.pi = LatexCmds['π'] = bind(NonSymbolaSymbol,'\\pi ','&pi;');
  3454. LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','&lambda;');
  3455. //uppercase greek letters
  3456. LatexCmds.Upsilon = //LaTeX
  3457. LatexCmds.Upsi = //Elsevier and 9573-13
  3458. LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
  3459. LatexCmds.Upsih = //'cos it makes sense to me
  3460. bind(Symbol,'\\Upsilon ','<var style="font-family: serif">&upsih;</var>'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :(
  3461. //other symbols with the same LaTeX command and HTML character entity reference
  3462. LatexCmds.Gamma =
  3463. LatexCmds.Delta =
  3464. LatexCmds.Theta =
  3465. LatexCmds.Lambda =
  3466. LatexCmds.Xi =
  3467. LatexCmds.Pi =
  3468. LatexCmds.Sigma =
  3469. LatexCmds.Phi =
  3470. LatexCmds.Psi =
  3471. LatexCmds.Omega =
  3472. LatexCmds.forall = P(VanillaSymbol, function(_, super_) {
  3473. _.init = function(latex) {
  3474. super_.init.call(this,'\\'+latex+' ','&'+latex+';');
  3475. };
  3476. });
  3477. // symbols that aren't a single MathCommand, but are instead a whole
  3478. // Fragment. Creates the Fragment from a LaTeX string
  3479. var LatexFragment = P(MathCommand, function(_) {
  3480. _.init = function(latex) { this.latex = latex; };
  3481. _.createLeftOf = function(cursor) {
  3482. var block = latexMathParser.parse(this.latex);
  3483. block.children().adopt(cursor.parent, cursor[L], cursor[R]);
  3484. cursor[L] = block.ends[R];
  3485. block.jQize().insertBefore(cursor.jQ);
  3486. block.finalizeInsert(cursor.options, cursor);
  3487. if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
  3488. if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
  3489. cursor.parent.bubble('reflow');
  3490. };
  3491. _.parser = function() {
  3492. var frag = latexMathParser.parse(this.latex).children();
  3493. return Parser.succeed(frag);
  3494. };
  3495. });
  3496. // for what seems to me like [stupid reasons][1], Unicode provides
  3497. // subscripted and superscripted versions of all ten Arabic numerals,
  3498. // as well as [so-called "vulgar fractions"][2].
  3499. // Nobody really cares about most of them, but some of them actually
  3500. // predate Unicode, dating back to [ISO-8859-1][3], apparently also
  3501. // known as "Latin-1", which among other things [Windows-1252][4]
  3502. // largely coincides with, so Microsoft Word sometimes inserts them
  3503. // and they get copy-pasted into MathQuill.
  3504. //
  3505. // (Irrelevant but funny story: though not a superset of Latin-1 aka
  3506. // ISO-8859-1, Windows-1252 **is** a strict superset of the "closely
  3507. // related but distinct"[3] "ISO 8859-1" -- see the lack of a dash
  3508. // after "ISO"? Completely different character set, like elephants vs
  3509. // elephant seals, or "Zombies" vs "Zombie Redneck Torture Family".
  3510. // What kind of idiot would get them confused.
  3511. // People in fact got them confused so much, it was so common to
  3512. // mislabel Windows-1252 text as ISO-8859-1, that most modern web
  3513. // browsers and email clients treat the MIME charset of ISO-8859-1
  3514. // as actually Windows-1252, behavior now standard in the HTML5 spec.)
  3515. //
  3516. // [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_andsuper_scripts
  3517. // [2]: http://en.wikipedia.org/wiki/Number_Forms
  3518. // [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
  3519. // [4]: http://en.wikipedia.org/wiki/Windows-1252
  3520. LatexCmds['¹'] = bind(LatexFragment, '^1');
  3521. LatexCmds['²'] = bind(LatexFragment, '^2');
  3522. LatexCmds['³'] = bind(LatexFragment, '^3');
  3523. LatexCmds['¼'] = bind(LatexFragment, '\\frac14');
  3524. LatexCmds['½'] = bind(LatexFragment, '\\frac12');
  3525. LatexCmds['¾'] = bind(LatexFragment, '\\frac34');
  3526. var PlusMinus = P(BinaryOperator, function(_) {
  3527. _.init = VanillaSymbol.prototype.init;
  3528. _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
  3529. if (dir === R) return; // ignore if sibling only changed on the right
  3530. this.jQ[0].className =
  3531. (!this[L] || this[L] instanceof BinaryOperator ? '' : 'mq-binary-operator');
  3532. return this;
  3533. };
  3534. });
  3535. LatexCmds['+'] = bind(PlusMinus, '+', '+');
  3536. //yes, these are different dashes, I think one is an en dash and the other is a hyphen
  3537. LatexCmds['–'] = LatexCmds['-'] = bind(PlusMinus, '-', '&minus;');
  3538. LatexCmds['±'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus =
  3539. bind(PlusMinus,'\\pm ','&plusmn;');
  3540. LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus =
  3541. bind(PlusMinus,'\\mp ','&#8723;');
  3542. CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
  3543. bind(BinaryOperator, '\\cdot ', '&middot;', '*');
  3544. //semantically should be &sdot;, but &middot; looks better
  3545. var Inequality = P(BinaryOperator, function(_, super_) {
  3546. _.init = function(data, strict) {
  3547. this.data = data;
  3548. this.strict = strict;
  3549. var strictness = (strict ? 'Strict' : '');
  3550. super_.init.call(this, data['ctrlSeq'+strictness], data['html'+strictness],
  3551. data['text'+strictness]);
  3552. };
  3553. _.swap = function(strict) {
  3554. this.strict = strict;
  3555. var strictness = (strict ? 'Strict' : '');
  3556. this.ctrlSeq = this.data['ctrlSeq'+strictness];
  3557. this.jQ.html(this.data['html'+strictness]);
  3558. this.textTemplate = [ this.data['text'+strictness] ];
  3559. };
  3560. _.deleteTowards = function(dir, cursor) {
  3561. if (dir === L && !this.strict) {
  3562. this.swap(true);
  3563. this.bubble('reflow');
  3564. return;
  3565. }
  3566. super_.deleteTowards.apply(this, arguments);
  3567. };
  3568. });
  3569. var less = { ctrlSeq: '\\le ', html: '&le;', text: '≤',
  3570. ctrlSeqStrict: '<', htmlStrict: '&lt;', textStrict: '<' };
  3571. var greater = { ctrlSeq: '\\ge ', html: '&ge;', text: '≥',
  3572. ctrlSeqStrict: '>', htmlStrict: '&gt;', textStrict: '>' };
  3573. LatexCmds['<'] = LatexCmds.lt = bind(Inequality, less, true);
  3574. LatexCmds['>'] = LatexCmds.gt = bind(Inequality, greater, true);
  3575. LatexCmds['≤'] = LatexCmds.le = LatexCmds.leq = bind(Inequality, less, false);
  3576. LatexCmds['≥'] = LatexCmds.ge = LatexCmds.geq = bind(Inequality, greater, false);
  3577. var Equality = P(BinaryOperator, function(_, super_) {
  3578. _.init = function() {
  3579. super_.init.call(this, '=', '=');
  3580. };
  3581. _.createLeftOf = function(cursor) {
  3582. if (cursor[L] instanceof Inequality && cursor[L].strict) {
  3583. cursor[L].swap(false);
  3584. cursor[L].bubble('reflow');
  3585. return;
  3586. }
  3587. super_.createLeftOf.apply(this, arguments);
  3588. };
  3589. });
  3590. LatexCmds['='] = Equality;
  3591. LatexCmds['×'] = LatexCmds.times = bind(BinaryOperator, '\\times ', '&times;', '[x]');
  3592. LatexCmds['÷'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides =
  3593. bind(BinaryOperator,'\\div ','&divide;', '[/]');
  3594. CharCmds['~'] = LatexCmds.sim = bind(BinaryOperator, '\\sim ', '~', '~');
  3595. /***************************
  3596. * Commands and Operators.
  3597. **************************/
  3598. var scale, // = function(jQ, x, y) { ... }
  3599. //will use a CSS 2D transform to scale the jQuery-wrapped HTML elements,
  3600. //or the filter matrix transform fallback for IE 5.5-8, or gracefully degrade to
  3601. //increasing the fontSize to match the vertical Y scaling factor.
  3602. //ideas from http://github.com/louisremi/jquery.transform.js
  3603. //see also http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx
  3604. forceIERedraw = noop,
  3605. div = document.createElement('div'),
  3606. div_style = div.style,
  3607. transformPropNames = {
  3608. transform:1,
  3609. WebkitTransform:1,
  3610. MozTransform:1,
  3611. OTransform:1,
  3612. msTransform:1
  3613. },
  3614. transformPropName;
  3615. for (var prop in transformPropNames) {
  3616. if (prop in div_style) {
  3617. transformPropName = prop;
  3618. break;
  3619. }
  3620. }
  3621. if (transformPropName) {
  3622. scale = function(jQ, x, y) {
  3623. jQ.css(transformPropName, 'scale('+x+','+y+')');
  3624. };
  3625. }
  3626. else if ('filter' in div_style) { //IE 6, 7, & 8 fallback, see https://github.com/laughinghan/mathquill/wiki/Transforms
  3627. forceIERedraw = function(el){ el.className = el.className; };
  3628. scale = function(jQ, x, y) { //NOTE: assumes y > x
  3629. x /= (1+(y-1)/2);
  3630. jQ.css('fontSize', y + 'em');
  3631. if (!jQ.hasClass('mq-matrixed-container')) {
  3632. jQ.addClass('mq-matrixed-container')
  3633. .wrapInner('<span class="mq-matrixed"></span>');
  3634. }
  3635. var innerjQ = jQ.children()
  3636. .css('filter', 'progid:DXImageTransform.Microsoft'
  3637. + '.Matrix(M11=' + x + ",SizingMethod='auto expand')"
  3638. );
  3639. function calculateMarginRight() {
  3640. jQ.css('marginRight', (innerjQ.width()-1)*(x-1)/x + 'px');
  3641. }
  3642. calculateMarginRight();
  3643. var intervalId = setInterval(calculateMarginRight);
  3644. $(window).load(function() {
  3645. clearTimeout(intervalId);
  3646. calculateMarginRight();
  3647. });
  3648. };
  3649. }
  3650. else {
  3651. scale = function(jQ, x, y) {
  3652. jQ.css('fontSize', y + 'em');
  3653. };
  3654. }
  3655. var Style = P(MathCommand, function(_, super_) {
  3656. _.init = function(ctrlSeq, tagName, attrs) {
  3657. super_.init.call(this, ctrlSeq, '<'+tagName+' '+attrs+'>&0</'+tagName+'>');
  3658. };
  3659. });
  3660. //fonts
  3661. LatexCmds.mathrm = bind(Style, '\\mathrm', 'span', 'class="mq-roman mq-font"');
  3662. LatexCmds.mathit = bind(Style, '\\mathit', 'i', 'class="mq-font"');
  3663. LatexCmds.mathbf = bind(Style, '\\mathbf', 'b', 'class="mq-font"');
  3664. LatexCmds.mathsf = bind(Style, '\\mathsf', 'span', 'class="mq-sans-serif mq-font"');
  3665. LatexCmds.mathtt = bind(Style, '\\mathtt', 'span', 'class="mq-monospace mq-font"');
  3666. //text-decoration
  3667. LatexCmds.underline = bind(Style, '\\underline', 'span', 'class="mq-non-leaf mq-underline"');
  3668. LatexCmds.overline = LatexCmds.bar = bind(Style, '\\overline', 'span', 'class="mq-non-leaf mq-overline"');
  3669. LatexCmds.overrightarrow = bind(Style, '\\overrightarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-right"');
  3670. LatexCmds.overleftarrow = bind(Style, '\\overleftarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-left"');
  3671. // `\textcolor{color}{math}` will apply a color to the given math content, where
  3672. // `color` is any valid CSS Color Value (see [SitePoint docs][] (recommended),
  3673. // [Mozilla docs][], or [W3C spec][]).
  3674. //
  3675. // [SitePoint docs]: http://reference.sitepoint.com/css/colorvalues
  3676. // [Mozilla docs]: https://developer.mozilla.org/en-US/docs/CSS/color_value#Values
  3677. // [W3C spec]: http://dev.w3.org/csswg/css3-color/#colorunits
  3678. var TextColor = LatexCmds.textcolor = P(MathCommand, function(_, super_) {
  3679. _.setColor = function(color) {
  3680. this.color = color;
  3681. this.htmlTemplate =
  3682. '<span class="mq-textcolor" style="color:' + color + '">&0</span>';
  3683. };
  3684. _.latex = function() {
  3685. return '\\textcolor{' + this.color + '}{' + this.blocks[0].latex() + '}';
  3686. };
  3687. _.parser = function() {
  3688. var self = this;
  3689. var optWhitespace = Parser.optWhitespace;
  3690. var string = Parser.string;
  3691. var regex = Parser.regex;
  3692. return optWhitespace
  3693. .then(string('{'))
  3694. .then(regex(/^[#\w\s.,()%-]*/))
  3695. .skip(string('}'))
  3696. .then(function(color) {
  3697. self.setColor(color);
  3698. return super_.parser.call(self);
  3699. })
  3700. ;
  3701. };
  3702. });
  3703. // Very similar to the \textcolor command, but will add the given CSS class.
  3704. // Usage: \class{classname}{math}
  3705. // Note regex that whitelists valid CSS classname characters:
  3706. // https://github.com/mathquill/mathquill/pull/191#discussion_r4327442
  3707. var Class = LatexCmds['class'] = P(MathCommand, function(_, super_) {
  3708. _.parser = function() {
  3709. var self = this, string = Parser.string, regex = Parser.regex;
  3710. return Parser.optWhitespace
  3711. .then(string('{'))
  3712. .then(regex(/^[-\w\s\\\xA0-\xFF]*/))
  3713. .skip(string('}'))
  3714. .then(function(cls) {
  3715. self.htmlTemplate = '<span class="mq-class '+cls+'">&0</span>';
  3716. return super_.parser.call(self);
  3717. })
  3718. ;
  3719. };
  3720. });
  3721. var SupSub = P(MathCommand, function(_, super_) {
  3722. _.ctrlSeq = '_{...}^{...}';
  3723. _.createLeftOf = function(cursor) {
  3724. if (!cursor[L] && cursor.options.supSubsRequireOperand) return;
  3725. return super_.createLeftOf.apply(this, arguments);
  3726. };
  3727. _.contactWeld = function(cursor) {
  3728. // Look on either side for a SupSub, if one is found compare my
  3729. // .sub, .sup with its .sub, .sup. If I have one that it doesn't,
  3730. // then call .addBlock() on it with my block; if I have one that
  3731. // it also has, then insert my block's children into its block,
  3732. // unless my block has none, in which case insert the cursor into
  3733. // its block (and not mine, I'm about to remove myself) in the case
  3734. // I was just typed.
  3735. // TODO: simplify
  3736. // equiv. to [L, R].forEach(function(dir) { ... });
  3737. for (var dir = L; dir; dir = (dir === L ? R : false)) {
  3738. if (this[dir] instanceof SupSub) {
  3739. // equiv. to 'sub sup'.split(' ').forEach(function(supsub) { ... });
  3740. for (var supsub = 'sub'; supsub; supsub = (supsub === 'sub' ? 'sup' : false)) {
  3741. var src = this[supsub], dest = this[dir][supsub];
  3742. if (!src) continue;
  3743. if (!dest) this[dir].addBlock(src.disown());
  3744. else if (!src.isEmpty()) { // ins src children at -dir end of dest
  3745. src.jQ.children().insAtDirEnd(-dir, dest.jQ);
  3746. var children = src.children().disown();
  3747. var pt = Point(dest, children.ends[R], dest.ends[L]);
  3748. if (dir === L) children.adopt(dest, dest.ends[R], 0);
  3749. else children.adopt(dest, 0, dest.ends[L]);
  3750. }
  3751. else var pt = Point(dest, 0, dest.ends[L]);
  3752. this.placeCursor = (function(dest, src) { // TODO: don't monkey-patch
  3753. return function(cursor) { cursor.insAtDirEnd(-dir, dest || src); };
  3754. }(dest, src));
  3755. }
  3756. this.remove();
  3757. if (cursor && cursor[L] === this) {
  3758. if (dir === R && pt) {
  3759. pt[L] ? cursor.insRightOf(pt[L]) : cursor.insAtLeftEnd(pt.parent);
  3760. }
  3761. else cursor.insRightOf(this[dir]);
  3762. }
  3763. break;
  3764. }
  3765. }
  3766. this.respace();
  3767. };
  3768. Options.p.charsThatBreakOutOfSupSub = '';
  3769. _.finalizeTree = function() {
  3770. this.ends[L].write = function(cursor, ch) {
  3771. if (cursor.options.autoSubscriptNumerals && this === this.parent.sub) {
  3772. if (ch === '_') return;
  3773. var cmd = this.chToCmd(ch);
  3774. if (cmd instanceof Symbol) cursor.deleteSelection();
  3775. else cursor.clearSelection().insRightOf(this.parent);
  3776. return cmd.createLeftOf(cursor.show());
  3777. }
  3778. if (cursor[L] && !cursor[R] && !cursor.selection
  3779. && cursor.options.charsThatBreakOutOfSupSub.indexOf(ch) > -1) {
  3780. cursor.insRightOf(this.parent);
  3781. }
  3782. MathBlock.p.write.apply(this, arguments);
  3783. };
  3784. };
  3785. _.moveTowards = function(dir, cursor, updown) {
  3786. if (cursor.options.autoSubscriptNumerals && !this.sup) {
  3787. cursor.insDirOf(dir, this);
  3788. }
  3789. else super_.moveTowards.apply(this, arguments);
  3790. };
  3791. _.deleteTowards = function(dir, cursor) {
  3792. if (cursor.options.autoSubscriptNumerals && this.sub) {
  3793. var cmd = this.sub.ends[-dir];
  3794. if (cmd instanceof Symbol) cmd.remove();
  3795. else if (cmd) cmd.deleteTowards(dir, cursor.insAtDirEnd(-dir, this.sub));
  3796. // TODO: factor out a .removeBlock() or something
  3797. if (this.sub.isEmpty()) {
  3798. this.sub.deleteOutOf(L, cursor.insAtLeftEnd(this.sub));
  3799. if (this.sup) cursor.insDirOf(-dir, this);
  3800. // Note `-dir` because in e.g. x_1^2| want backspacing (leftward)
  3801. // to delete the 1 but to end up rightward of x^2; with non-negated
  3802. // `dir` (try it), the cursor appears to have gone "through" the ^2.
  3803. }
  3804. }
  3805. else super_.deleteTowards.apply(this, arguments);
  3806. };
  3807. _.latex = function() {
  3808. function latex(prefix, block) {
  3809. var l = block && block.latex();
  3810. return block ? prefix + (l.length === 1 ? l : '{' + (l || ' ') + '}') : '';
  3811. }
  3812. return latex('_', this.sub) + latex('^', this.sup);
  3813. };
  3814. _.respace = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
  3815. if (dir === R) return; // ignore if sibling only changed on the right
  3816. this.jQ.toggleClass('mq-limit', this[L].ctrlSeq === '\\int ');
  3817. };
  3818. _.addBlock = function(block) {
  3819. if (this.supsub === 'sub') {
  3820. this.sup = this.upInto = this.sub.upOutOf = block;
  3821. block.adopt(this, this.sub, 0).downOutOf = this.sub;
  3822. block.jQ = $('<span class="mq-sup"/>').append(block.jQ.children())
  3823. .attr(mqBlockId, block.id).prependTo(this.jQ);
  3824. }
  3825. else {
  3826. this.sub = this.downInto = this.sup.downOutOf = block;
  3827. block.adopt(this, 0, this.sup).upOutOf = this.sup;
  3828. block.jQ = $('<span class="mq-sub"></span>').append(block.jQ.children())
  3829. .attr(mqBlockId, block.id).appendTo(this.jQ.removeClass('mq-sup-only'));
  3830. this.jQ.append('<span style="display:inline-block;width:0">&#8203;</span>');
  3831. }
  3832. // like 'sub sup'.split(' ').forEach(function(supsub) { ... });
  3833. for (var i = 0; i < 2; i += 1) (function(cmd, supsub, oppositeSupsub, updown) {
  3834. cmd[supsub].deleteOutOf = function(dir, cursor) {
  3835. cursor.insDirOf((this[dir] ? -dir : dir), this.parent);
  3836. if (!this.isEmpty()) {
  3837. var end = this.ends[dir];
  3838. this.children().disown()
  3839. .withDirAdopt(dir, cursor.parent, cursor[dir], cursor[-dir])
  3840. .jQ.insDirOf(-dir, cursor.jQ);
  3841. cursor[-dir] = end;
  3842. }
  3843. cmd.supsub = oppositeSupsub;
  3844. delete cmd[supsub];
  3845. delete cmd[updown+'Into'];
  3846. cmd[oppositeSupsub][updown+'OutOf'] = insLeftOfMeUnlessAtEnd;
  3847. delete cmd[oppositeSupsub].deleteOutOf;
  3848. if (supsub === 'sub') $(cmd.jQ.addClass('mq-sup-only')[0].lastChild).remove();
  3849. this.remove();
  3850. };
  3851. }(this, 'sub sup'.split(' ')[i], 'sup sub'.split(' ')[i], 'down up'.split(' ')[i]));
  3852. };
  3853. });
  3854. function insLeftOfMeUnlessAtEnd(cursor) {
  3855. // cursor.insLeftOf(cmd), unless cursor at the end of block, and every
  3856. // ancestor cmd is at the end of every ancestor block
  3857. var cmd = this.parent, ancestorCmd = cursor;
  3858. do {
  3859. if (ancestorCmd[R]) return cursor.insLeftOf(cmd);
  3860. ancestorCmd = ancestorCmd.parent.parent;
  3861. } while (ancestorCmd !== cmd);
  3862. cursor.insRightOf(cmd);
  3863. }
  3864. LatexCmds.subscript =
  3865. LatexCmds._ = P(SupSub, function(_, super_) {
  3866. _.supsub = 'sub';
  3867. _.htmlTemplate =
  3868. '<span class="mq-supsub mq-non-leaf">'
  3869. + '<span class="mq-sub">&0</span>'
  3870. + '<span style="display:inline-block;width:0">&#8203;</span>'
  3871. + '</span>'
  3872. ;
  3873. _.textTemplate = [ '_' ];
  3874. _.finalizeTree = function() {
  3875. this.downInto = this.sub = this.ends[L];
  3876. this.sub.upOutOf = insLeftOfMeUnlessAtEnd;
  3877. super_.finalizeTree.call(this);
  3878. };
  3879. });
  3880. LatexCmds.superscript =
  3881. LatexCmds.supscript =
  3882. LatexCmds['^'] = P(SupSub, function(_, super_) {
  3883. _.supsub = 'sup';
  3884. _.htmlTemplate =
  3885. '<span class="mq-supsub mq-non-leaf mq-sup-only">'
  3886. + '<span class="mq-sup">&0</span>'
  3887. + '</span>'
  3888. ;
  3889. _.textTemplate = [ '^' ];
  3890. _.finalizeTree = function() {
  3891. this.upInto = this.sup = this.ends[R];
  3892. this.sup.downOutOf = insLeftOfMeUnlessAtEnd;
  3893. super_.finalizeTree.call(this);
  3894. };
  3895. });
  3896. var SummationNotation = P(MathCommand, function(_, super_) {
  3897. _.init = function(ch, html) {
  3898. var htmlTemplate =
  3899. '<span class="mq-large-operator mq-non-leaf">'
  3900. + '<span class="mq-to"><span>&1</span></span>'
  3901. + '<big>'+html+'</big>'
  3902. + '<span class="mq-from"><span>&0</span></span>'
  3903. + '</span>'
  3904. ;
  3905. Symbol.prototype.init.call(this, ch, htmlTemplate);
  3906. };
  3907. _.createLeftOf = function(cursor) {
  3908. super_.createLeftOf.apply(this, arguments);
  3909. if (cursor.options.sumStartsWithNEquals) {
  3910. Letter('n').createLeftOf(cursor);
  3911. Equality().createLeftOf(cursor);
  3912. }
  3913. };
  3914. _.latex = function() {
  3915. function simplify(latex) {
  3916. return latex.length === 1 ? latex : '{' + (latex || ' ') + '}';
  3917. }
  3918. return this.ctrlSeq + '_' + simplify(this.ends[L].latex()) +
  3919. '^' + simplify(this.ends[R].latex());
  3920. };
  3921. _.parser = function() {
  3922. var string = Parser.string;
  3923. var optWhitespace = Parser.optWhitespace;
  3924. var succeed = Parser.succeed;
  3925. var block = latexMathParser.block;
  3926. var self = this;
  3927. var blocks = self.blocks = [ MathBlock(), MathBlock() ];
  3928. for (var i = 0; i < blocks.length; i += 1) {
  3929. blocks[i].adopt(self, self.ends[R], 0);
  3930. }
  3931. return optWhitespace.then(string('_').or(string('^'))).then(function(supOrSub) {
  3932. var child = blocks[supOrSub === '_' ? 0 : 1];
  3933. return block.then(function(block) {
  3934. block.children().adopt(child, child.ends[R], 0);
  3935. return succeed(self);
  3936. });
  3937. }).many().result(self);
  3938. };
  3939. _.finalizeTree = function() {
  3940. this.downInto = this.ends[L];
  3941. this.upInto = this.ends[R];
  3942. this.ends[L].upOutOf = this.ends[R];
  3943. this.ends[R].downOutOf = this.ends[L];
  3944. };
  3945. });
  3946. LatexCmds['∑'] =
  3947. LatexCmds.sum =
  3948. LatexCmds.summation = bind(SummationNotation,'\\sum ','&sum;');
  3949. LatexCmds['∏'] =
  3950. LatexCmds.prod =
  3951. LatexCmds.product = bind(SummationNotation,'\\prod ','&prod;');
  3952. LatexCmds.coprod =
  3953. LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','&#8720;');
  3954. var Fraction =
  3955. LatexCmds.frac =
  3956. LatexCmds.dfrac =
  3957. LatexCmds.cfrac =
  3958. LatexCmds.fraction = P(MathCommand, function(_, super_) {
  3959. _.ctrlSeq = '\\frac';
  3960. _.htmlTemplate =
  3961. '<span class="mq-fraction mq-non-leaf">'
  3962. + '<span class="mq-numerator">&0</span>'
  3963. + '<span class="mq-denominator">&1</span>'
  3964. + '<span style="display:inline-block;width:0">&#8203;</span>'
  3965. + '</span>'
  3966. ;
  3967. _.textTemplate = ['(', ')/(', ')'];
  3968. _.finalizeTree = function() {
  3969. this.upInto = this.ends[R].upOutOf = this.ends[L];
  3970. this.downInto = this.ends[L].downOutOf = this.ends[R];
  3971. };
  3972. });
  3973. var LiveFraction =
  3974. LatexCmds.over =
  3975. CharCmds['/'] = P(Fraction, function(_, super_) {
  3976. _.createLeftOf = function(cursor) {
  3977. if (!this.replacedFragment) {
  3978. var leftward = cursor[L];
  3979. while (leftward &&
  3980. !(
  3981. leftward instanceof BinaryOperator ||
  3982. leftward instanceof (LatexCmds.text || noop) ||
  3983. leftward instanceof SummationNotation ||
  3984. leftward.ctrlSeq === '\\ ' ||
  3985. /^[,;:]$/.test(leftward.ctrlSeq)
  3986. ) //lookbehind for operator
  3987. ) leftward = leftward[L];
  3988. if (leftward instanceof SummationNotation && leftward[R] instanceof SupSub) {
  3989. leftward = leftward[R];
  3990. if (leftward[R] instanceof SupSub && leftward[R].ctrlSeq != leftward.ctrlSeq)
  3991. leftward = leftward[R];
  3992. }
  3993. if (leftward !== cursor[L]) {
  3994. this.replaces(Fragment(leftward[R] || cursor.parent.ends[L], cursor[L]));
  3995. cursor[L] = leftward;
  3996. }
  3997. }
  3998. super_.createLeftOf.call(this, cursor);
  3999. };
  4000. });
  4001. var SquareRoot =
  4002. LatexCmds.sqrt =
  4003. LatexCmds['√'] = P(MathCommand, function(_, super_) {
  4004. _.ctrlSeq = '\\sqrt';
  4005. _.htmlTemplate =
  4006. '<span class="mq-non-leaf">'
  4007. + '<span class="mq-scaled mq-sqrt-prefix">&radic;</span>'
  4008. + '<span class="mq-non-leaf mq-sqrt-stem">&0</span>'
  4009. + '</span>'
  4010. ;
  4011. _.textTemplate = ['sqrt(', ')'];
  4012. _.parser = function() {
  4013. return latexMathParser.optBlock.then(function(optBlock) {
  4014. return latexMathParser.block.map(function(block) {
  4015. var nthroot = NthRoot();
  4016. nthroot.blocks = [ optBlock, block ];
  4017. optBlock.adopt(nthroot, 0, 0);
  4018. block.adopt(nthroot, optBlock, 0);
  4019. return nthroot;
  4020. });
  4021. }).or(super_.parser.call(this));
  4022. };
  4023. _.reflow = function() {
  4024. var block = this.ends[R].jQ;
  4025. scale(block.prev(), 1, block.innerHeight()/+block.css('fontSize').slice(0,-2) - .1);
  4026. };
  4027. });
  4028. var Vec = LatexCmds.vec = P(MathCommand, function(_, super_) {
  4029. _.ctrlSeq = '\\vec';
  4030. _.htmlTemplate =
  4031. '<span class="mq-non-leaf">'
  4032. + '<span class="mq-vector-prefix">&rarr;</span>'
  4033. + '<span class="mq-vector-stem">&0</span>'
  4034. + '</span>'
  4035. ;
  4036. _.textTemplate = ['vec(', ')'];
  4037. });
  4038. var NthRoot =
  4039. LatexCmds.nthroot = P(SquareRoot, function(_, super_) {
  4040. _.htmlTemplate =
  4041. '<sup class="mq-nthroot mq-non-leaf">&0</sup>'
  4042. + '<span class="mq-scaled">'
  4043. + '<span class="mq-sqrt-prefix mq-scaled">&radic;</span>'
  4044. + '<span class="mq-sqrt-stem mq-non-leaf">&1</span>'
  4045. + '</span>'
  4046. ;
  4047. _.textTemplate = ['sqrt[', '](', ')'];
  4048. _.latex = function() {
  4049. return '\\sqrt['+this.ends[L].latex()+']{'+this.ends[R].latex()+'}';
  4050. };
  4051. });
  4052. function DelimsMixin(_, super_) {
  4053. _.jQadd = function() {
  4054. super_.jQadd.apply(this, arguments);
  4055. this.delimjQs = this.jQ.children(':first').add(this.jQ.children(':last'));
  4056. this.contentjQ = this.jQ.children(':eq(1)');
  4057. };
  4058. _.reflow = function() {
  4059. var height = this.contentjQ.outerHeight()
  4060. / parseFloat(this.contentjQ.css('fontSize'));
  4061. scale(this.delimjQs, min(1 + .2*(height - 1), 1.2), 1.2*height);
  4062. };
  4063. }
  4064. // Round/Square/Curly/Angle Brackets (aka Parens/Brackets/Braces)
  4065. // first typed as one-sided bracket with matching "ghost" bracket at
  4066. // far end of current block, until you type an opposing one
  4067. var Bracket = P(P(MathCommand, DelimsMixin), function(_, super_) {
  4068. _.init = function(side, open, close, ctrlSeq, end) {
  4069. super_.init.call(this, '\\left'+ctrlSeq, undefined, [open, close]);
  4070. this.side = side;
  4071. this.sides = {};
  4072. this.sides[L] = { ch: open, ctrlSeq: ctrlSeq };
  4073. this.sides[R] = { ch: close, ctrlSeq: end };
  4074. };
  4075. _.numBlocks = function() { return 1; };
  4076. _.html = function() { // wait until now so that .side may
  4077. this.htmlTemplate = // be set by createLeftOf or parser
  4078. '<span class="mq-non-leaf">'
  4079. + '<span class="mq-scaled mq-paren'+(this.side === R ? ' mq-ghost' : '')+'">'
  4080. + this.sides[L].ch
  4081. + '</span>'
  4082. + '<span class="mq-non-leaf">&0</span>'
  4083. + '<span class="mq-scaled mq-paren'+(this.side === L ? ' mq-ghost' : '')+'">'
  4084. + this.sides[R].ch
  4085. + '</span>'
  4086. + '</span>'
  4087. ;
  4088. return super_.html.call(this);
  4089. };
  4090. _.latex = function() {
  4091. return '\\left'+this.sides[L].ctrlSeq+this.ends[L].latex()+'\\right'+this.sides[R].ctrlSeq;
  4092. };
  4093. _.oppBrack = function(opts, node, expectedSide) {
  4094. // return node iff it's a 1-sided bracket of expected side (if any, may be
  4095. // undefined), and of opposite side from me if I'm not a pipe
  4096. return node instanceof Bracket && node.side && node.side !== -expectedSide
  4097. && (this.sides[this.side].ch === '|' || node.side === -this.side)
  4098. && (!opts.restrictMismatchedBrackets
  4099. || OPP_BRACKS[this.sides[this.side].ch] === node.sides[node.side].ch
  4100. || { '(': ']', '[': ')' }[this.sides[L].ch] === node.sides[R].ch) && node;
  4101. };
  4102. _.closeOpposing = function(brack) {
  4103. brack.side = 0;
  4104. brack.sides[this.side] = this.sides[this.side]; // copy over my info (may be
  4105. brack.delimjQs.eq(this.side === L ? 0 : 1) // mismatched, like [a, b))
  4106. .removeClass('mq-ghost').html(this.sides[this.side].ch);
  4107. };
  4108. _.createLeftOf = function(cursor) {
  4109. if (!this.replacedFragment) { // unless wrapping seln in brackets,
  4110. // check if next to or inside an opposing one-sided bracket
  4111. // (must check both sides 'cos I might be a pipe)
  4112. var opts = cursor.options;
  4113. var brack = this.oppBrack(opts, cursor[L], L)
  4114. || this.oppBrack(opts, cursor[R], R)
  4115. || this.oppBrack(opts, cursor.parent.parent);
  4116. }
  4117. if (brack) {
  4118. var side = this.side = -brack.side; // may be pipe with .side not yet set
  4119. this.closeOpposing(brack);
  4120. if (brack === cursor.parent.parent && cursor[side]) { // move the stuff between
  4121. Fragment(cursor[side], cursor.parent.ends[side], -side) // me and ghost outside
  4122. .disown().withDirAdopt(-side, brack.parent, brack, brack[side])
  4123. .jQ.insDirOf(side, brack.jQ);
  4124. brack.bubble('reflow');
  4125. }
  4126. }
  4127. else {
  4128. brack = this, side = brack.side;
  4129. if (brack.replacedFragment) brack.side = 0; // wrapping seln, don't be one-sided
  4130. else if (cursor[-side]) { // elsewise, auto-expand so ghost is at far end
  4131. brack.replaces(Fragment(cursor[-side], cursor.parent.ends[-side], side));
  4132. cursor[-side] = 0;
  4133. }
  4134. super_.createLeftOf.call(brack, cursor);
  4135. }
  4136. if (side === L) cursor.insAtLeftEnd(brack.ends[L]);
  4137. else cursor.insRightOf(brack);
  4138. };
  4139. _.placeCursor = noop;
  4140. _.unwrap = function() {
  4141. this.ends[L].children().disown().adopt(this.parent, this, this[R])
  4142. .jQ.insertAfter(this.jQ);
  4143. this.remove();
  4144. };
  4145. _.deleteSide = function(side, outward, cursor) {
  4146. var parent = this.parent, sib = this[side], farEnd = parent.ends[side];
  4147. if (side === this.side) { // deleting non-ghost of one-sided bracket, unwrap
  4148. this.unwrap();
  4149. sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
  4150. return;
  4151. }
  4152. var opts = cursor.options, wasSolid = !this.side;
  4153. this.side = -side;
  4154. // if deleting like, outer close-brace of [(1+2)+3} where inner open-paren
  4155. if (this.oppBrack(opts, this.ends[L].ends[this.side], side)) { // is ghost,
  4156. this.closeOpposing(this.ends[L].ends[this.side]); // then become [1+2)+3
  4157. var origEnd = this.ends[L].ends[side];
  4158. this.unwrap();
  4159. if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
  4160. sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
  4161. }
  4162. else { // if deleting like, inner close-brace of ([1+2}+3) where outer
  4163. if (this.oppBrack(opts, this.parent.parent, side)) { // open-paren is
  4164. this.parent.parent.closeOpposing(this); // ghost, then become [1+2+3)
  4165. this.parent.parent.unwrap();
  4166. } // else if deleting outward from a solid pair, unwrap
  4167. else if (outward && wasSolid) {
  4168. this.unwrap();
  4169. sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
  4170. return;
  4171. }
  4172. else { // else deleting just one of a pair of brackets, become one-sided
  4173. this.sides[side] = { ch: OPP_BRACKS[this.sides[this.side].ch],
  4174. ctrlSeq: OPP_BRACKS[this.sides[this.side].ctrlSeq] };
  4175. this.delimjQs.removeClass('mq-ghost')
  4176. .eq(side === L ? 0 : 1).addClass('mq-ghost').html(this.sides[side].ch);
  4177. }
  4178. if (sib) { // auto-expand so ghost is at far end
  4179. var origEnd = this.ends[L].ends[side];
  4180. Fragment(sib, farEnd, -side).disown()
  4181. .withDirAdopt(-side, this.ends[L], origEnd, 0)
  4182. .jQ.insAtDirEnd(side, this.ends[L].jQ.removeClass('mq-empty'));
  4183. if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
  4184. cursor.insDirOf(-side, sib);
  4185. } // didn't auto-expand, cursor goes just outside or just inside parens
  4186. else (outward ? cursor.insDirOf(side, this)
  4187. : cursor.insAtDirEnd(side, this.ends[L]));
  4188. }
  4189. };
  4190. _.deleteTowards = function(dir, cursor) {
  4191. this.deleteSide(-dir, false, cursor);
  4192. };
  4193. _.finalizeTree = function() {
  4194. this.ends[L].deleteOutOf = function(dir, cursor) {
  4195. this.parent.deleteSide(dir, true, cursor);
  4196. };
  4197. // FIXME HACK: after initial creation/insertion, finalizeTree would only be
  4198. // called if the paren is selected and replaced, e.g. by LiveFraction
  4199. this.finalizeTree = this.intentionalBlur = function() {
  4200. this.delimjQs.eq(this.side === L ? 1 : 0).removeClass('mq-ghost');
  4201. this.side = 0;
  4202. };
  4203. };
  4204. _.siblingCreated = function(opts, dir) { // if something typed between ghost and far
  4205. if (dir === -this.side) this.finalizeTree(); // end of its block, solidify
  4206. };
  4207. });
  4208. var OPP_BRACKS = {
  4209. '(': ')',
  4210. ')': '(',
  4211. '[': ']',
  4212. ']': '[',
  4213. '{': '}',
  4214. '}': '{',
  4215. '\\{': '\\}',
  4216. '\\}': '\\{',
  4217. '&lang;': '&rang;',
  4218. '&rang;': '&lang;',
  4219. '\\langle ': '\\rangle ',
  4220. '\\rangle ': '\\langle ',
  4221. '|': '|'
  4222. };
  4223. function bindCharBracketPair(open, ctrlSeq) {
  4224. var ctrlSeq = ctrlSeq || open, close = OPP_BRACKS[open], end = OPP_BRACKS[ctrlSeq];
  4225. CharCmds[open] = bind(Bracket, L, open, close, ctrlSeq, end);
  4226. CharCmds[close] = bind(Bracket, R, open, close, ctrlSeq, end);
  4227. }
  4228. bindCharBracketPair('(');
  4229. bindCharBracketPair('[');
  4230. bindCharBracketPair('{', '\\{');
  4231. LatexCmds.langle = bind(Bracket, L, '&lang;', '&rang;', '\\langle ', '\\rangle ');
  4232. LatexCmds.rangle = bind(Bracket, R, '&lang;', '&rang;', '\\langle ', '\\rangle ');
  4233. CharCmds['|'] = bind(Bracket, L, '|', '|', '|', '|');
  4234. LatexCmds.left = P(MathCommand, function(_) {
  4235. _.parser = function() {
  4236. var regex = Parser.regex;
  4237. var string = Parser.string;
  4238. var succeed = Parser.succeed;
  4239. var optWhitespace = Parser.optWhitespace;
  4240. return optWhitespace.then(regex(/^(?:[([|]|\\\{)/))
  4241. .then(function(ctrlSeq) { // TODO: \langle, \rangle
  4242. var open = (ctrlSeq.charAt(0) === '\\' ? ctrlSeq.slice(1) : ctrlSeq);
  4243. return latexMathParser.then(function (block) {
  4244. return string('\\right').skip(optWhitespace)
  4245. .then(regex(/^(?:[\])|]|\\\})/)).map(function(end) {
  4246. var close = (end.charAt(0) === '\\' ? end.slice(1) : end);
  4247. var cmd = Bracket(0, open, close, ctrlSeq, end);
  4248. cmd.blocks = [ block ];
  4249. block.adopt(cmd, 0, 0);
  4250. return cmd;
  4251. })
  4252. ;
  4253. });
  4254. })
  4255. ;
  4256. };
  4257. });
  4258. LatexCmds.right = P(MathCommand, function(_) {
  4259. _.parser = function() {
  4260. return Parser.fail('unmatched \\right');
  4261. };
  4262. });
  4263. var Binomial =
  4264. LatexCmds.binom =
  4265. LatexCmds.binomial = P(P(MathCommand, DelimsMixin), function(_, super_) {
  4266. _.ctrlSeq = '\\binom';
  4267. _.htmlTemplate =
  4268. '<span class="mq-non-leaf">'
  4269. + '<span class="mq-paren mq-scaled">(</span>'
  4270. + '<span class="mq-non-leaf">'
  4271. + '<span class="mq-array mq-non-leaf">'
  4272. + '<span>&0</span>'
  4273. + '<span>&1</span>'
  4274. + '</span>'
  4275. + '</span>'
  4276. + '<span class="mq-paren mq-scaled">)</span>'
  4277. + '</span>'
  4278. ;
  4279. _.textTemplate = ['choose(',',',')'];
  4280. });
  4281. var Choose =
  4282. LatexCmds.choose = P(Binomial, function(_) {
  4283. _.createLeftOf = LiveFraction.prototype.createLeftOf;
  4284. });
  4285. LatexCmds.editable = // backcompat with before cfd3620 on #233
  4286. LatexCmds.MathQuillMathField = P(MathCommand, function(_, super_) {
  4287. _.ctrlSeq = '\\MathQuillMathField';
  4288. _.htmlTemplate =
  4289. '<span class="mq-editable-field">'
  4290. + '<span class="mq-root-block">&0</span>'
  4291. + '</span>'
  4292. ;
  4293. _.parser = function() {
  4294. var self = this,
  4295. string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
  4296. return string('[').then(regex(/^[a-z][a-z0-9]*/i)).skip(string(']'))
  4297. .map(function(name) { self.name = name; }).or(succeed())
  4298. .then(super_.parser.call(self));
  4299. };
  4300. _.finalizeTree = function() {
  4301. var ctrlr = Controller(this.ends[L], this.jQ, Options());
  4302. ctrlr.KIND_OF_MQ = 'MathField';
  4303. ctrlr.editable = true;
  4304. ctrlr.createTextarea();
  4305. ctrlr.editablesTextareaEvents();
  4306. ctrlr.cursor.insAtRightEnd(ctrlr.root);
  4307. RootBlockMixin(ctrlr.root);
  4308. };
  4309. _.registerInnerField = function(innerFields, MathField) {
  4310. innerFields.push(innerFields[this.name] = MathField(this.ends[L].controller));
  4311. };
  4312. _.latex = function(){ return this.ends[L].latex(); };
  4313. _.text = function(){ return this.ends[L].text(); };
  4314. });
  4315. // Embed arbitrary things
  4316. // Probably the closest DOM analogue would be an iframe?
  4317. // From MathQuill's perspective, it's a Symbol, it can be
  4318. // anywhere and the cursor can go around it but never in it.
  4319. // Create by calling public API method .dropEmbedded(),
  4320. // or by calling the global public API method .registerEmbed()
  4321. // and rendering LaTeX like \embed{registeredName} (see test).
  4322. var Embed = LatexCmds.embed = P(Symbol, function(_, super_) {
  4323. _.setOptions = function(options) {
  4324. function noop () { return ""; }
  4325. this.text = options.text || noop;
  4326. this.htmlTemplate = options.htmlString || "";
  4327. this.latex = options.latex || noop;
  4328. return this;
  4329. };
  4330. _.parser = function() {
  4331. var self = this;
  4332. string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
  4333. return string('{').then(regex(/^[a-z][a-z0-9]*/i)).skip(string('}'))
  4334. .then(function(name) {
  4335. // the chars allowed in the optional data block are arbitrary other than
  4336. // excluding curly braces and square brackets (which'd be too confusing)
  4337. return string('[').then(regex(/^[-\w\s]*/)).skip(string(']'))
  4338. .or(succeed()).map(function(data) {
  4339. return self.setOptions(EMBEDS[name](data));
  4340. })
  4341. ;
  4342. })
  4343. ;
  4344. };
  4345. });
  4346. suite('SupSub', function() {
  4347. var mq;
  4348. setup(function() {
  4349. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  4350. });
  4351. teardown(function() {
  4352. $(mq.el()).remove();
  4353. });
  4354. function prayWellFormedPoint(pt) { prayWellFormed(pt.parent, pt[L], pt[R]); }
  4355. var expecteds = [
  4356. 'x_{ab} x_{ba}, x_a^b x_a^b; x_{ab} x_{ba}, x_a^b x_a^b; x_a x_a, x_a^{} x_a^{}',
  4357. 'x_b^a x_b^a, x^{ab} x^{ba}; x_b^a x_b^a, x^{ab} x^{ba}; x_{}^a x_{}^a, x^a x^a'
  4358. ];
  4359. var expectedsAfterC = [
  4360. 'x_{abc} x_{bca}, x_a^{bc} x_a^{bc}; x_{ab}c x_{bca}, x_a^bc x_a^bc; x_ac x_{ca}, x_a^{}c x_a^{}c',
  4361. 'x_{bc}^a x_{bc}^a, x^{abc} x^{bca}; x_b^ac x_b^ac, x^{ab}c x^{bca}; x_{}^ac x_{}^ac, x^ac x^{ca}'
  4362. ];
  4363. 'sub super'.split(' ').forEach(function(initSupsub, i) {
  4364. var initialLatex = 'x_a x^a'.split(' ')[i];
  4365. 'typed, wrote, wrote empty'.split(', ').forEach(function(did, j) {
  4366. var doTo = [
  4367. function(mq, supsub) { mq.typedText(supsub).typedText('b'); },
  4368. function(mq, supsub) { mq.write(supsub+'b'); },
  4369. function(mq, supsub) { mq.write(supsub+'{}'); }
  4370. ][j];
  4371. 'sub super'.split(' ').forEach(function(supsub, k) {
  4372. var cmd = '_^'.split('')[k];
  4373. 'after before'.split(' ').forEach(function(side, l) {
  4374. var moveToSide = [
  4375. noop,
  4376. function(mq) { mq.moveToLeftEnd().keystroke('Right'); }
  4377. ][l];
  4378. var expected = expecteds[i].split('; ')[j].split(', ')[k].split(' ')[l];
  4379. var expectedAfterC = expectedsAfterC[i].split('; ')[j].split(', ')[k].split(' ')[l];
  4380. test('initial '+initSupsub+'script then '+did+' '+supsub+'script '+side, function() {
  4381. mq.latex(initialLatex);
  4382. assert.equal(mq.latex(), initialLatex);
  4383. moveToSide(mq);
  4384. doTo(mq, cmd);
  4385. assert.equal(mq.latex().replace(/ /g, ''), expected);
  4386. prayWellFormedPoint(mq.__controller.cursor);
  4387. mq.typedText('c');
  4388. assert.equal(mq.latex().replace(/ /g, ''), expectedAfterC);
  4389. });
  4390. });
  4391. });
  4392. });
  4393. });
  4394. var expecteds = 'x_a^3 x_a^3, x_a^3 x_a^3; x^{a3} x^{3a}, x^{a3} x^{3a}';
  4395. var expectedsAfterC = 'x_a^3c x_a^3c, x_a^3c x_a^3c; x^{a3}c x^{3ca}, x^{a3}c x^{3ca}';
  4396. 'sub super'.split(' ').forEach(function(initSupsub, i) {
  4397. var initialLatex = 'x_a x^a'.split(' ')[i];
  4398. 'typed wrote'.split(' ').forEach(function(did, j) {
  4399. var doTo = [
  4400. function(mq) { mq.typedText('³'); },
  4401. function(mq) { mq.write('³'); }
  4402. ][j];
  4403. 'after before'.split(' ').forEach(function(side, k) {
  4404. var moveToSide = [
  4405. noop,
  4406. function(mq) { mq.moveToLeftEnd().keystroke('Right'); }
  4407. ][k];
  4408. var expected = expecteds.split('; ')[i].split(', ')[j].split(' ')[k];
  4409. var expectedAfterC = expectedsAfterC.split('; ')[i].split(', ')[j].split(' ')[k];
  4410. test('initial '+initSupsub+'script then '+did+' \'³\' '+side, function() {
  4411. mq.latex(initialLatex);
  4412. assert.equal(mq.latex(), initialLatex);
  4413. moveToSide(mq);
  4414. doTo(mq);
  4415. assert.equal(mq.latex().replace(/ /g, ''), expected);
  4416. prayWellFormedPoint(mq.__controller.cursor);
  4417. mq.typedText('c');
  4418. assert.equal(mq.latex().replace(/ /g, ''), expectedAfterC);
  4419. });
  4420. });
  4421. });
  4422. });
  4423. test('render LaTeX with 2 SupSub\'s in a row', function() {
  4424. mq.latex('x_a_b');
  4425. assert.equal(mq.latex(), 'x_{ab}');
  4426. mq.latex('x_a_{}');
  4427. assert.equal(mq.latex(), 'x_a');
  4428. mq.latex('x_{}_a');
  4429. assert.equal(mq.latex(), 'x_a');
  4430. mq.latex('x^a^b');
  4431. assert.equal(mq.latex(), 'x^{ab}');
  4432. mq.latex('x^a^{}');
  4433. assert.equal(mq.latex(), 'x^a');
  4434. mq.latex('x^{}^a');
  4435. assert.equal(mq.latex(), 'x^a');
  4436. });
  4437. test('render LaTeX with 3 alternating SupSub\'s in a row', function() {
  4438. mq.latex('x_a^b_c');
  4439. assert.equal(mq.latex(), 'x_{ac}^b');
  4440. mq.latex('x^a_b^c');
  4441. assert.equal(mq.latex(), 'x_b^{ac}');
  4442. });
  4443. suite('deleting', function() {
  4444. test('backspacing out of and then re-typing subscript', function() {
  4445. mq.latex('x_a^b');
  4446. assert.equal(mq.latex(), 'x_a^b');
  4447. mq.keystroke('Down Backspace');
  4448. assert.equal(mq.latex(), 'x_{ }^b');
  4449. mq.keystroke('Backspace');
  4450. assert.equal(mq.latex(), 'x^b');
  4451. mq.typedText('_a');
  4452. assert.equal(mq.latex(), 'x_a^b');
  4453. mq.keystroke('Left Backspace');
  4454. assert.equal(mq.latex(), 'xa^b');
  4455. mq.typedText('c');
  4456. assert.equal(mq.latex(), 'xca^b');
  4457. });
  4458. test('backspacing out of and then re-typing superscript', function() {
  4459. mq.latex('x_a^b');
  4460. assert.equal(mq.latex(), 'x_a^b');
  4461. mq.keystroke('Up Backspace');
  4462. assert.equal(mq.latex(), 'x_a^{ }');
  4463. mq.keystroke('Backspace');
  4464. assert.equal(mq.latex(), 'x_a');
  4465. mq.typedText('^b');
  4466. assert.equal(mq.latex(), 'x_a^b');
  4467. mq.keystroke('Left Backspace');
  4468. assert.equal(mq.latex(), 'x_ab');
  4469. mq.typedText('c');
  4470. assert.equal(mq.latex(), 'x_acb');
  4471. });
  4472. });
  4473. });
  4474. suite('autoOperatorNames', function() {
  4475. var mq;
  4476. setup(function() {
  4477. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  4478. });
  4479. teardown(function() {
  4480. $(mq.el()).remove();
  4481. });
  4482. function assertLatex(input, expected) {
  4483. var result = mq.latex();
  4484. assert.equal(result, expected,
  4485. input+', got \''+result+'\', expected \''+expected+'\''
  4486. );
  4487. }
  4488. test('simple LaTeX parsing, typing', function() {
  4489. function assertAutoOperatorNamesWork(str, latex) {
  4490. var count = 0;
  4491. var _autoUnItalicize = Letter.prototype.autoUnItalicize;
  4492. Letter.prototype.autoUnItalicize = function() {
  4493. count += 1;
  4494. return _autoUnItalicize.apply(this, arguments);
  4495. };
  4496. mq.latex(str);
  4497. assertLatex('parsing \''+str+'\'', latex);
  4498. assert.equal(count, 1);
  4499. mq.latex(latex);
  4500. assertLatex('parsing \''+latex+'\'', latex);
  4501. assert.equal(count, 2);
  4502. mq.latex('');
  4503. for (var i = 0; i < str.length; i += 1) mq.typedText(str.charAt(i));
  4504. assertLatex('typing \''+str+'\'', latex);
  4505. assert.equal(count, 2 + str.length);
  4506. }
  4507. assertAutoOperatorNamesWork('sin', '\\sin');
  4508. assertAutoOperatorNamesWork('inf', '\\inf');
  4509. assertAutoOperatorNamesWork('arcosh', '\\operatorname{arcosh}');
  4510. assertAutoOperatorNamesWork('acosh', 'a\\cosh');
  4511. assertAutoOperatorNamesWork('cosine', '\\cos ine');
  4512. assertAutoOperatorNamesWork('arcosecant', 'ar\\operatorname{cosec}ant');
  4513. assertAutoOperatorNamesWork('cscscscscscsc', '\\csc s\\csc s\\csc sc');
  4514. assertAutoOperatorNamesWork('scscscscscsc', 's\\csc s\\csc s\\csc');
  4515. });
  4516. test('deleting', function() {
  4517. var count = 0;
  4518. var _autoUnItalicize = Letter.prototype.autoUnItalicize;
  4519. Letter.prototype.autoUnItalicize = function() {
  4520. count += 1;
  4521. return _autoUnItalicize.apply(this, arguments);
  4522. };
  4523. var str = 'cscscscscscsc';
  4524. for (var i = 0; i < str.length; i += 1) mq.typedText(str.charAt(i));
  4525. assertLatex('typing \''+str+'\'', '\\csc s\\csc s\\csc sc');
  4526. assert.equal(count, str.length);
  4527. mq.moveToLeftEnd().keystroke('Del');
  4528. assertLatex('deleted first char', 's\\csc s\\csc s\\csc');
  4529. assert.equal(count, str.length + 1);
  4530. mq.typedText('c');
  4531. assertLatex('typed back first char', '\\csc s\\csc s\\csc sc');
  4532. assert.equal(count, str.length + 2);
  4533. mq.typedText('+');
  4534. assertLatex('typed plus to interrupt sequence of letters', 'c+s\\csc s\\csc s\\csc');
  4535. assert.equal(count, str.length + 4);
  4536. mq.keystroke('Backspace');
  4537. assertLatex('deleted plus', '\\csc s\\csc s\\csc sc');
  4538. assert.equal(count, str.length + 5);
  4539. });
  4540. suite('override autoOperatorNames', function() {
  4541. test('basic', function() {
  4542. MQ.config({ autoOperatorNames: 'sin lol' });
  4543. mq.typedText('arcsintrololol');
  4544. assert.equal(mq.latex(), 'arc\\sin tro\\operatorname{lol}ol');
  4545. });
  4546. test('command contains non-letters', function() {
  4547. assert.throws(function() { MQ.config({ autoOperatorNames: 'e1' }); });
  4548. });
  4549. test('command length less than 2', function() {
  4550. assert.throws(function() { MQ.config({ autoOperatorNames: 'e' }); });
  4551. });
  4552. suite('command list not perfectly space-delimited', function() {
  4553. test('double space', function() {
  4554. assert.throws(function() { MQ.config({ autoOperatorNames: 'pi theta' }); });
  4555. });
  4556. test('leading space', function() {
  4557. assert.throws(function() { MQ.config({ autoOperatorNames: ' pi' }); });
  4558. });
  4559. test('trailing space', function() {
  4560. assert.throws(function() { MQ.config({ autoOperatorNames: 'pi ' }); });
  4561. });
  4562. });
  4563. });
  4564. });
  4565. suite('autoSubscript', function() {
  4566. var mq;
  4567. setup(function() {
  4568. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0], {autoSubscriptNumerals: true});
  4569. rootBlock = mq.__controller.root;
  4570. controller = mq.__controller;
  4571. cursor = controller.cursor;
  4572. });
  4573. teardown(function() {
  4574. $(mq.el()).remove();
  4575. });
  4576. test('auto subscripting variables', function() {
  4577. mq.latex('x');
  4578. mq.typedText('2');
  4579. assert.equal(mq.latex(), 'x_2');
  4580. mq.typedText('3');
  4581. assert.equal(mq.latex(), 'x_{23}');
  4582. });
  4583. test('do not autosubscript functions', function() {
  4584. mq.latex('sin');
  4585. mq.typedText('2');
  4586. assert.equal(mq.latex(), '\\sin2');
  4587. mq.typedText('3');
  4588. assert.equal(mq.latex(), '\\sin23');
  4589. });
  4590. test('autosubscript exponentiated variables', function() {
  4591. mq.latex('x^2');
  4592. mq.typedText('2');
  4593. assert.equal(mq.latex(), 'x_2^2');
  4594. mq.typedText('3');
  4595. assert.equal(mq.latex(), 'x_{23}^2');
  4596. });
  4597. test('do not autosubscript exponentiated functions', function() {
  4598. mq.latex('sin^{2}');
  4599. mq.typedText('2');
  4600. assert.equal(mq.latex(), '\\sin^22');
  4601. mq.typedText('3');
  4602. assert.equal(mq.latex(), '\\sin^223');
  4603. });
  4604. test('do not autosubscript subscripted functions', function() {
  4605. mq.latex('sin_{10}');
  4606. mq.typedText('2');
  4607. assert.equal(mq.latex(), '\\sin_{10}2');
  4608. });
  4609. test('backspace through compound subscript', function() {
  4610. mq.latex('x_{2_2}');
  4611. //first backspace moves to cursor in subscript and peels it off
  4612. mq.keystroke('Backspace');
  4613. assert.equal(mq.latex(),'x_2');
  4614. //second backspace clears out remaining subscript
  4615. mq.keystroke('Backspace');
  4616. assert.equal(mq.latex(),'x_{ }');
  4617. //unpeel subscript
  4618. mq.keystroke('Backspace');
  4619. assert.equal(mq.latex(),'x');
  4620. });
  4621. test('backspace through simple subscript', function() {
  4622. mq.latex('x_{2+3}');
  4623. assert.equal(cursor.parent, rootBlock, 'start in the root block');
  4624. //backspace peels off subscripts but stays at the root block level
  4625. mq.keystroke('Backspace');
  4626. assert.equal(mq.latex(),'x_{2+}');
  4627. assert.equal(cursor.parent, rootBlock, 'backspace keeps us in the root block');
  4628. mq.keystroke('Backspace');
  4629. assert.equal(mq.latex(),'x_2');
  4630. assert.equal(cursor.parent, rootBlock, 'backspace keeps us in the root block');
  4631. //second backspace clears out remaining subscript and unpeels
  4632. mq.keystroke('Backspace');
  4633. assert.equal(mq.latex(),'x');
  4634. });
  4635. test('backspace through subscript & superscript with autosubscripting on', function() {
  4636. mq.latex('x_2^{32}');
  4637. //first backspace peels off the subscript
  4638. mq.keystroke('Backspace');
  4639. assert.equal(mq.latex(),'x^{32}');
  4640. //second backspace goes into the exponent
  4641. mq.keystroke('Backspace');
  4642. assert.equal(mq.latex(),'x^{32}');
  4643. //clear out exponent
  4644. mq.keystroke('Backspace');
  4645. mq.keystroke('Backspace');
  4646. assert.equal(mq.latex(),'x^{ }');
  4647. //unpeel exponent
  4648. mq.keystroke('Backspace');
  4649. assert.equal(mq.latex(),'x');
  4650. });
  4651. });
  4652. suite('backspace', function() {
  4653. var mq, rootBlock, controller, cursor;
  4654. setup(function() {
  4655. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  4656. rootBlock = mq.__controller.root;
  4657. controller = mq.__controller;
  4658. cursor = controller.cursor;
  4659. });
  4660. teardown(function() {
  4661. $(mq.el()).remove();
  4662. });
  4663. function prayWellFormedPoint(pt) { prayWellFormed(pt.parent, pt[L], pt[R]); }
  4664. function assertLatex(latex) {
  4665. prayWellFormedPoint(mq.__controller.cursor);
  4666. assert.equal(mq.latex(), latex);
  4667. }
  4668. test('backspace through exponent', function() {
  4669. controller.renderLatexMath('x^{nm}');
  4670. var exp = rootBlock.ends[R],
  4671. expBlock = exp.ends[L];
  4672. assert.equal(exp.latex(), '^{nm}', 'right end el is exponent');
  4673. assert.equal(cursor.parent, rootBlock, 'cursor is in root block');
  4674. assert.equal(cursor[L], exp, 'cursor is at the end of root block');
  4675. mq.keystroke('Backspace');
  4676. assert.equal(cursor.parent, expBlock, 'cursor up goes into exponent on backspace');
  4677. assertLatex('x^{nm}');
  4678. mq.keystroke('Backspace');
  4679. assert.equal(cursor.parent, expBlock, 'cursor still in exponent');
  4680. assertLatex('x^n');
  4681. mq.keystroke('Backspace');
  4682. assert.equal(cursor.parent, expBlock, 'still in exponent, but it is empty');
  4683. assertLatex('x^{ }');
  4684. mq.keystroke('Backspace');
  4685. assert.equal(cursor.parent, rootBlock, 'backspace tears down exponent');
  4686. assertLatex('x');
  4687. });
  4688. test('backspace through complex fraction', function() {
  4689. controller.renderLatexMath('1+\\frac{1}{\\frac{1}{2}+\\frac{2}{3}}');
  4690. //first backspace moves to denominator
  4691. mq.keystroke('Backspace');
  4692. assertLatex('1+\\frac{1}{\\frac{1}{2}+\\frac{2}{3}}');
  4693. //first backspace moves to denominator in denominator
  4694. mq.keystroke('Backspace');
  4695. assertLatex('1+\\frac{1}{\\frac{1}{2}+\\frac{2}{3}}');
  4696. //finally delete a character
  4697. mq.keystroke('Backspace');
  4698. assertLatex('1+\\frac{1}{\\frac{1}{2}+\\frac{2}{ }}');
  4699. //destroy fraction
  4700. mq.keystroke('Backspace');
  4701. assertLatex('1+\\frac{1}{\\frac{1}{2}+2}');
  4702. mq.keystroke('Backspace');
  4703. mq.keystroke('Backspace');
  4704. assertLatex('1+\\frac{1}{\\frac{1}{2}}');
  4705. mq.keystroke('Backspace');
  4706. mq.keystroke('Backspace');
  4707. assertLatex('1+\\frac{1}{\\frac{1}{ }}');
  4708. mq.keystroke('Backspace');
  4709. assertLatex('1+\\frac{1}{1}');
  4710. mq.keystroke('Backspace');
  4711. assertLatex('1+\\frac{1}{ }');
  4712. mq.keystroke('Backspace');
  4713. assertLatex('1+1');
  4714. });
  4715. test('backspace through compound subscript', function() {
  4716. mq.latex('x_{2_2}');
  4717. //first backspace goes into the subscript
  4718. mq.keystroke('Backspace');
  4719. assert.equal(mq.latex(),'x_{2_2}');
  4720. //second one goes into the subscripts' subscript
  4721. mq.keystroke('Backspace');
  4722. assert.equal(mq.latex(),'x_{2_2}');
  4723. mq.keystroke('Backspace');
  4724. assert.equal(mq.latex(),'x_{2_{ }}');
  4725. mq.keystroke('Backspace');
  4726. assert.equal(mq.latex(),'x_2');
  4727. mq.keystroke('Backspace');
  4728. assert.equal(mq.latex(),'x_{ }');
  4729. mq.keystroke('Backspace');
  4730. assert.equal(mq.latex(),'x');
  4731. });
  4732. test('backspace through simple subscript', function() {
  4733. mq.latex('x_{2+3}');
  4734. assert.equal(cursor.parent, rootBlock, 'start in the root block');
  4735. //backspace goes down
  4736. mq.keystroke('Backspace');
  4737. assert.equal(mq.latex(),'x_{2+3}');
  4738. mq.keystroke('Backspace');
  4739. assert.equal(mq.latex(),'x_{2+}');
  4740. mq.keystroke('Backspace');
  4741. assert.equal(mq.latex(),'x_2');
  4742. mq.keystroke('Backspace');
  4743. assert.equal(mq.latex(),'x_{ }');
  4744. mq.keystroke('Backspace');
  4745. assert.equal(mq.latex(),'x');
  4746. });
  4747. test('backspace through subscript & superscript', function() {
  4748. mq.latex('x_2^{32}');
  4749. //first backspace takes us into the exponent
  4750. mq.keystroke('Backspace');
  4751. assert.equal(mq.latex(),'x_2^{32}');
  4752. //second backspace is within the exponent
  4753. mq.keystroke('Backspace');
  4754. assert.equal(mq.latex(),'x_2^3');
  4755. //clear out exponent
  4756. mq.keystroke('Backspace');
  4757. assert.equal(mq.latex(),'x_2^{ }');
  4758. //unpeel exponent
  4759. mq.keystroke('Backspace');
  4760. assert.equal(mq.latex(),'x_2');
  4761. //into subscript
  4762. mq.keystroke('Backspace');
  4763. assert.equal(mq.latex(),'x_2');
  4764. //clear out subscript
  4765. mq.keystroke('Backspace');
  4766. assert.equal(mq.latex(),'x_{ }');
  4767. //unpeel exponent
  4768. mq.keystroke('Backspace');
  4769. assert.equal(mq.latex(),'x');
  4770. //clear out math field
  4771. mq.keystroke('Backspace');
  4772. assert.equal(mq.latex(),'');
  4773. });
  4774. test('backspace through nthroot', function() {
  4775. mq.latex('\\sqrt[3]{x}');
  4776. //first backspace takes us inside the nthroot
  4777. mq.keystroke('Backspace');
  4778. assert.equal(mq.latex(),'\\sqrt[3]{x}');
  4779. //second backspace removes the x
  4780. mq.keystroke('Backspace');
  4781. assert.equal(mq.latex(),'\\sqrt[3]{}');
  4782. //third one destroys the cube root, but leaves behind the 3
  4783. mq.keystroke('Backspace');
  4784. assert.equal(mq.latex(),'3');
  4785. mq.keystroke('Backspace');
  4786. assert.equal(mq.latex(),'');
  4787. });
  4788. test('backspace through large operator', function() {
  4789. mq.latex('\\sum_{n=1}^3x');
  4790. //first backspace takes out the argument
  4791. mq.keystroke('Backspace');
  4792. assert.equal(mq.latex(),'\\sum_{n=1}^3');
  4793. //up into the superscript
  4794. mq.keystroke('Backspace');
  4795. assert.equal(mq.latex(),'\\sum_{n=1}^3');
  4796. //up into the superscript
  4797. mq.keystroke('Backspace');
  4798. assert.equal(mq.latex(),'\\sum_{n=1}^{ }');
  4799. //destroy the sum, preserve the subscript (a little surprising)
  4800. mq.keystroke('Backspace');
  4801. assert.equal(mq.latex(),'n=1');
  4802. });
  4803. test('backspace into text block', function() {
  4804. mq.latex('\\text{x}');
  4805. mq.keystroke('Backspace');
  4806. var textBlock = rootBlock.ends[R];
  4807. assert.equal(cursor.parent, textBlock, 'cursor is in text block');
  4808. assert.equal(cursor[R], 0, 'cursor is at the end of text block');
  4809. assert.equal(cursor[L].text, 'x', 'cursor is rightward of the x');
  4810. });
  4811. suite('empties', function() {
  4812. test('backspace empty exponent', function() {
  4813. mq.latex('x^{}');
  4814. mq.keystroke('Backspace');
  4815. assert.equal(mq.latex(), 'x');
  4816. });
  4817. test('backspace empty sqrt', function() {
  4818. mq.latex('1+\\sqrt{}');
  4819. mq.keystroke('Backspace');
  4820. assert.equal(mq.latex(), '1+');
  4821. });
  4822. test('backspace empty fraction', function() {
  4823. mq.latex('1+\\frac{}{}');
  4824. mq.keystroke('Backspace');
  4825. assert.equal(mq.latex(), '1+');
  4826. });
  4827. });
  4828. });
  4829. suite('CSS', function() {
  4830. test('math field doesn\'t fuck up ancestor\'s .scrollWidth', function() {
  4831. var mock = $('#mock').css({
  4832. fontSize: '16px',
  4833. height: '25px', // must be greater than font-size * 115% + 2 * 2px (padding) + 2 * 1px (border)
  4834. width: '25px'
  4835. })[0];
  4836. assert.equal(mock.scrollHeight, 25);
  4837. assert.equal(mock.scrollWidth, 25);
  4838. var mq = MQ.MathField($('<span style="box-sizing:border-box;height:100%;width:100%"></span>').appendTo(mock)[0]);
  4839. assert.equal(mock.scrollHeight, 25);
  4840. assert.equal(mock.scrollWidth, 25);
  4841. $(mq.el()).remove();
  4842. $(mock).css({
  4843. fontSize: '',
  4844. height: '',
  4845. width: ''
  4846. });
  4847. });
  4848. test('empty root block does not collapse', function() {
  4849. var testEl = $('<span></span>').appendTo('#mock');
  4850. var mq = MQ.MathField(testEl[0]);
  4851. var rootEl = testEl.find('.mq-root-block');
  4852. assert.ok(rootEl.hasClass('mq-empty'), 'Empty root block should have the mq-empty class name.');
  4853. assert.ok(rootEl.height() > 0, 'Empty root block height should be above 0.');
  4854. testEl.remove();
  4855. });
  4856. test('empty block does not collapse', function() {
  4857. var testEl = $('<span>\\frac{}{}</span>').appendTo('#mock');
  4858. var mq = MQ.MathField(testEl[0]);
  4859. var numeratorEl = testEl.find('.mq-numerator');
  4860. assert.ok(numeratorEl.hasClass('mq-empty'), 'Empty numerator should have the mq-empty class name.');
  4861. assert.ok(numeratorEl.height() > 0, 'Empty numerator height should be above 0.');
  4862. testEl.remove();
  4863. });
  4864. test('test florin spacing', function () {
  4865. var mq,
  4866. mock = $('#mock');
  4867. mq = MathQuill.MathField($('<span></span>').appendTo(mock)[0]);
  4868. mq.typedText("f'");
  4869. var mqF = $(mq.el()).find('.mq-f');
  4870. var testVal = parseFloat(mqF.css('margin-right')) - parseFloat(mqF.css('margin-left'));
  4871. assert.ok(testVal > 0, 'this should be truthy') ;
  4872. });
  4873. });
  4874. suite('focusBlur', function() {
  4875. function assertHasFocus(mq, name, invert) {
  4876. assert.ok(!!invert ^ $(mq.el()).find('textarea').is(':focus'), name + (invert ? ' does not have focus' : ' has focus'));
  4877. }
  4878. suite('handlers can shift focus away', function() {
  4879. var mq, mq2, wasUpOutOfCalled;
  4880. setup(function() {
  4881. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0], {
  4882. handlers: {
  4883. upOutOf: function() {
  4884. wasUpOutOfCalled = true;
  4885. mq2.focus();
  4886. }
  4887. }
  4888. });
  4889. mq2 = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  4890. wasUpOutOfCalled = false;
  4891. });
  4892. teardown(function() {
  4893. $(mq.el()).add(mq2.el()).remove();
  4894. });
  4895. function triggerUpOutOf(mq) {
  4896. $(mq.el()).find('textarea').trigger(jQuery.Event('keydown', { which: 38 }));
  4897. assert.ok(wasUpOutOfCalled);
  4898. }
  4899. test('normally', function() {
  4900. mq.focus();
  4901. assertHasFocus(mq, 'mq');
  4902. triggerUpOutOf(mq);
  4903. assertHasFocus(mq2, 'mq2');
  4904. });
  4905. test('even if there\'s a selection', function(done) {
  4906. mq.focus();
  4907. assertHasFocus(mq, 'mq');
  4908. mq.typedText('asdf');
  4909. assert.equal(mq.latex(), 'asdf');
  4910. mq.keystroke('Shift-Left');
  4911. setTimeout(function() {
  4912. assert.equal($(mq.el()).find('textarea').val(), 'f');
  4913. triggerUpOutOf(mq);
  4914. assertHasFocus(mq2, 'mq2');
  4915. done();
  4916. });
  4917. });
  4918. });
  4919. test('select behaves normally after blurring and re-focusing', function(done) {
  4920. var mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  4921. mq.focus();
  4922. assertHasFocus(mq, 'mq');
  4923. mq.typedText('asdf');
  4924. assert.equal(mq.latex(), 'asdf');
  4925. mq.keystroke('Shift-Left');
  4926. setTimeout(function() {
  4927. assert.equal($(mq.el()).find('textarea').val(), 'f');
  4928. mq.blur();
  4929. assertHasFocus(mq, 'mq', 'not');
  4930. setTimeout(function() {
  4931. assert.equal($(mq.el()).find('textarea').val(), '');
  4932. mq.focus();
  4933. assertHasFocus(mq, 'mq');
  4934. mq.keystroke('Shift-Left');
  4935. setTimeout(function() {
  4936. assert.equal($(mq.el()).find('textarea').val(), 'd');
  4937. $(mq.el()).remove();
  4938. done();
  4939. });
  4940. }, 10);
  4941. });
  4942. });
  4943. });
  4944. suite('HTML', function() {
  4945. function renderHtml(numBlocks, htmlTemplate) {
  4946. var cmd = {
  4947. id: 1,
  4948. blocks: Array(numBlocks),
  4949. htmlTemplate: htmlTemplate
  4950. };
  4951. for (var i = 0; i < numBlocks; i += 1) {
  4952. cmd.blocks[i] = {
  4953. i: i,
  4954. id: 2 + i,
  4955. join: function() { return 'Block:' + this.i; }
  4956. };
  4957. }
  4958. return MathCommand.prototype.html.call(cmd);
  4959. }
  4960. test('simple HTML templates', function() {
  4961. var htmlTemplate = '<span>A Symbol</span>';
  4962. var html = '<span mathquill-command-id=1>A Symbol</span>';
  4963. assert.equal(html, renderHtml(0, htmlTemplate), 'a symbol');
  4964. htmlTemplate = '<span>&0</span>';
  4965. html = '<span mathquill-command-id=1 mathquill-block-id=2>Block:0</span>';
  4966. assert.equal(html, renderHtml(1, htmlTemplate), 'same span is cmd and block');
  4967. htmlTemplate =
  4968. '<span>'
  4969. + '<span>&0</span>'
  4970. + '<span>&1</span>'
  4971. + '</span>'
  4972. ;
  4973. html =
  4974. '<span mathquill-command-id=1>'
  4975. + '<span mathquill-block-id=2>Block:0</span>'
  4976. + '<span mathquill-block-id=3>Block:1</span>'
  4977. + '</span>'
  4978. ;
  4979. assert.equal(html, renderHtml(2, htmlTemplate), 'container span with two block spans');
  4980. });
  4981. test('context-free HTML templates', function() {
  4982. var htmlTemplate = '<br/>';
  4983. var html = '<br mathquill-command-id=1/>';
  4984. assert.equal(html, renderHtml(0, htmlTemplate), 'self-closing tag');
  4985. htmlTemplate =
  4986. '<span>'
  4987. + '<span>&0</span>'
  4988. + '</span>'
  4989. + '<span>'
  4990. + '<span>&1</span>'
  4991. + '</span>'
  4992. ;
  4993. html =
  4994. '<span mathquill-command-id=1>'
  4995. + '<span mathquill-block-id=2>Block:0</span>'
  4996. + '</span>'
  4997. + '<span mathquill-command-id=1>'
  4998. + '<span mathquill-block-id=3>Block:1</span>'
  4999. + '</span>'
  5000. ;
  5001. assert.equal(html, renderHtml(2, htmlTemplate), 'two cmd spans');
  5002. htmlTemplate =
  5003. '<span></span>'
  5004. + '<span/>'
  5005. + '<span>'
  5006. + '<span>'
  5007. + '<span/>'
  5008. + '</span>'
  5009. + '<span>&1</span>'
  5010. + '<span/>'
  5011. + '<span></span>'
  5012. + '</span>'
  5013. + '<span>&0</span>'
  5014. ;
  5015. html =
  5016. '<span mathquill-command-id=1></span>'
  5017. + '<span mathquill-command-id=1/>'
  5018. + '<span mathquill-command-id=1>'
  5019. + '<span>'
  5020. + '<span/>'
  5021. + '</span>'
  5022. + '<span mathquill-block-id=3>Block:1</span>'
  5023. + '<span/>'
  5024. + '<span></span>'
  5025. + '</span>'
  5026. + '<span mathquill-command-id=1 mathquill-block-id=2>Block:0</span>'
  5027. ;
  5028. assert.equal(html, renderHtml(2, htmlTemplate), 'multiple nested cmd and block spans');
  5029. });
  5030. });
  5031. suite('latex', function() {
  5032. function assertParsesLatex(str, latex) {
  5033. if (arguments.length < 2) latex = str;
  5034. var result = latexMathParser.parse(str).postOrder('finalizeTree', Options.p).join('latex');
  5035. assert.equal(result, latex,
  5036. 'parsing \''+str+'\', got \''+result+'\', expected \''+latex+'\''
  5037. );
  5038. }
  5039. test('empty LaTeX', function () {
  5040. assertParsesLatex('');
  5041. assertParsesLatex(' ', '');
  5042. assertParsesLatex('{}', '');
  5043. assertParsesLatex(' {}{} {{{}} }', '');
  5044. });
  5045. test('variables', function() {
  5046. assertParsesLatex('xyz');
  5047. });
  5048. test('variables that can be mathbb', function() {
  5049. assertParsesLatex('PNZQRCH');
  5050. });
  5051. test('simple exponent', function() {
  5052. assertParsesLatex('x^n');
  5053. });
  5054. test('block exponent', function() {
  5055. assertParsesLatex('x^{n}', 'x^n');
  5056. assertParsesLatex('x^{nm}');
  5057. assertParsesLatex('x^{}', 'x^{ }');
  5058. });
  5059. test('nested exponents', function() {
  5060. assertParsesLatex('x^{n^m}');
  5061. });
  5062. test('exponents with spaces', function() {
  5063. assertParsesLatex('x^ 2', 'x^2');
  5064. assertParsesLatex('x ^2', 'x^2');
  5065. });
  5066. test('inner groups', function() {
  5067. assertParsesLatex('a{bc}d', 'abcd');
  5068. assertParsesLatex('{bc}d', 'bcd');
  5069. assertParsesLatex('a{bc}', 'abc');
  5070. assertParsesLatex('{bc}', 'bc');
  5071. assertParsesLatex('x^{a{bc}d}', 'x^{abcd}');
  5072. assertParsesLatex('x^{a{bc}}', 'x^{abc}');
  5073. assertParsesLatex('x^{{bc}}', 'x^{bc}');
  5074. assertParsesLatex('x^{{bc}d}', 'x^{bcd}');
  5075. assertParsesLatex('{asdf{asdf{asdf}asdf}asdf}', 'asdfasdfasdfasdfasdf');
  5076. });
  5077. test('commands without braces', function() {
  5078. assertParsesLatex('\\frac12', '\\frac{1}{2}');
  5079. assertParsesLatex('\\frac1a', '\\frac{1}{a}');
  5080. assertParsesLatex('\\frac ab', '\\frac{a}{b}');
  5081. assertParsesLatex('\\frac a b', '\\frac{a}{b}');
  5082. assertParsesLatex(' \\frac a b ', '\\frac{a}{b}');
  5083. assertParsesLatex('\\frac{1} 2', '\\frac{1}{2}');
  5084. assertParsesLatex('\\frac{ 1 } 2', '\\frac{1}{2}');
  5085. assert.throws(function() { latexMathParser.parse('\\frac'); });
  5086. });
  5087. test('whitespace', function() {
  5088. assertParsesLatex(' a + b ', 'a+b');
  5089. assertParsesLatex(' ', '');
  5090. assertParsesLatex('', '');
  5091. });
  5092. test('parens', function() {
  5093. var tree = latexMathParser.parse('\\left(123\\right)');
  5094. assert.ok(tree.ends[L] instanceof Bracket);
  5095. var contents = tree.ends[L].ends[L].join('latex');
  5096. assert.equal(contents, '123');
  5097. assert.equal(tree.join('latex'), '\\left(123\\right)');
  5098. });
  5099. test('parens with whitespace', function() {
  5100. assertParsesLatex('\\left ( 123 \\right ) ', '\\left(123\\right)');
  5101. });
  5102. test('escaped whitespace', function() {
  5103. assertParsesLatex('\\ ', '\\ ');
  5104. assertParsesLatex('\\ ', '\\ ');
  5105. assertParsesLatex(' \\ \\\t\t\t\\ \\\n\n\n', '\\ \\ \\ \\ ');
  5106. assertParsesLatex('\\space\\ \\ space ', '\\ \\ \\ space');
  5107. });
  5108. test('\\text', function() {
  5109. assertParsesLatex('\\text { lol! } ', '\\text{ lol! }');
  5110. assertParsesLatex('\\text{apples} \\ne \\text{oranges}',
  5111. '\\text{apples}\\ne \\text{oranges}');
  5112. });
  5113. test('not real LaTex commands, but valid symbols', function() {
  5114. assertParsesLatex('\\parallelogram ');
  5115. assertParsesLatex('\\circledot ', '\\odot ');
  5116. assertParsesLatex('\\degree ');
  5117. assertParsesLatex('\\square ');
  5118. });
  5119. suite('public API', function() {
  5120. var mq;
  5121. setup(function() {
  5122. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5123. });
  5124. teardown(function() {
  5125. $(mq.el()).remove();
  5126. });
  5127. suite('.latex(...)', function() {
  5128. function assertParsesLatex(str, latex) {
  5129. if (arguments.length < 2) latex = str;
  5130. mq.latex(str);
  5131. assert.equal(mq.latex(), latex);
  5132. }
  5133. test('basic rendering', function() {
  5134. assertParsesLatex('x = \\frac{ -b \\pm \\sqrt{ b^2 - 4ac } }{ 2a }',
  5135. 'x=\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}');
  5136. });
  5137. test('re-rendering', function() {
  5138. assertParsesLatex('a x^2 + b x + c = 0', 'ax^2+bx+c=0');
  5139. assertParsesLatex('x = \\frac{ -b \\pm \\sqrt{ b^2 - 4ac } }{ 2a }',
  5140. 'x=\\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}');
  5141. });
  5142. test('empty LaTeX', function () {
  5143. assertParsesLatex('');
  5144. assertParsesLatex(' ', '');
  5145. assertParsesLatex('{}', '');
  5146. assertParsesLatex(' {}{} {{{}} }', '');
  5147. });
  5148. test('coerces to a string', function () {
  5149. assertParsesLatex(undefined, 'undefined');
  5150. assertParsesLatex(null, 'null');
  5151. assertParsesLatex(0, '0');
  5152. assertParsesLatex(Infinity, 'Infinity');
  5153. assertParsesLatex(NaN, 'NaN');
  5154. assertParsesLatex(true, 'true');
  5155. assertParsesLatex(false, 'false');
  5156. assertParsesLatex({}, '[objectObject]'); // lol, the space gets ignored
  5157. assertParsesLatex({toString: function() { return 'thing'; }}, 'thing');
  5158. });
  5159. });
  5160. suite('.write(...)', function() {
  5161. test('empty LaTeX', function () {
  5162. function assertParsesLatex(str, latex) {
  5163. if (arguments.length < 2) latex = str;
  5164. mq.write(str);
  5165. assert.equal(mq.latex(), latex);
  5166. }
  5167. assertParsesLatex('');
  5168. assertParsesLatex(' ', '');
  5169. assertParsesLatex('{}', '');
  5170. assertParsesLatex(' {}{} {{{}} }', '');
  5171. });
  5172. test('overflow triggers automatic horizontal scroll', function(done) {
  5173. var mqEl = mq.el();
  5174. var rootEl = mq.__controller.root.jQ[0];
  5175. var cursor = mq.__controller.cursor;
  5176. $(mqEl).width(10);
  5177. var previousScrollLeft = rootEl.scrollLeft;
  5178. mq.write("abc");
  5179. setTimeout(afterScroll, 150);
  5180. function afterScroll() {
  5181. cursor.show();
  5182. try {
  5183. assert.ok(rootEl.scrollLeft > previousScrollLeft, "scrolls on write");
  5184. assert.ok(mqEl.getBoundingClientRect().right > cursor.jQ[0].getBoundingClientRect().right,
  5185. "cursor right end is inside the field");
  5186. }
  5187. catch(error) {
  5188. done(error);
  5189. return;
  5190. }
  5191. done();
  5192. }
  5193. });
  5194. suite('\\sum', function() {
  5195. test('basic', function() {
  5196. mq.write('\\sum_{n=0}^5');
  5197. assert.equal(mq.latex(), '\\sum_{n=0}^5');
  5198. mq.write('x^n');
  5199. assert.equal(mq.latex(), '\\sum_{n=0}^5x^n');
  5200. });
  5201. test('only lower bound', function() {
  5202. mq.write('\\sum_{n=0}');
  5203. assert.equal(mq.latex(), '\\sum_{n=0}^{ }');
  5204. mq.write('x^n');
  5205. assert.equal(mq.latex(), '\\sum_{n=0}^{ }x^n');
  5206. });
  5207. test('only upper bound', function() {
  5208. mq.write('\\sum^5');
  5209. assert.equal(mq.latex(), '\\sum_{ }^5');
  5210. mq.write('x^n');
  5211. assert.equal(mq.latex(), '\\sum_{ }^5x^n');
  5212. });
  5213. });
  5214. });
  5215. });
  5216. suite('\\MathQuillMathField', function() {
  5217. var outer, inner1, inner2;
  5218. setup(function() {
  5219. outer = MQ.StaticMath(
  5220. $('<span>\\frac{\\MathQuillMathField{x_0 + x_1 + x_2}}{\\MathQuillMathField{3}}</span>')
  5221. .appendTo('#mock')[0]
  5222. );
  5223. inner1 = outer.innerFields[0];
  5224. inner2 = outer.innerFields[1];
  5225. });
  5226. teardown(function() {
  5227. $(outer.el()).remove();
  5228. });
  5229. test('initial latex', function() {
  5230. assert.equal(inner1.latex(), 'x_0+x_1+x_2');
  5231. assert.equal(inner2.latex(), '3');
  5232. assert.equal(outer.latex(), '\\frac{x_0+x_1+x_2}{3}');
  5233. });
  5234. test('setting latex', function() {
  5235. inner1.latex('\\sum_{i=0}^N x_i');
  5236. inner2.latex('N');
  5237. assert.equal(inner1.latex(), '\\sum_{i=0}^Nx_i');
  5238. assert.equal(inner2.latex(), 'N');
  5239. assert.equal(outer.latex(), '\\frac{\\sum_{i=0}^Nx_i}{N}');
  5240. });
  5241. test('writing latex', function() {
  5242. inner1.write('+ x_3');
  5243. inner2.write('+ 1');
  5244. assert.equal(inner1.latex(), 'x_0+x_1+x_2+x_3');
  5245. assert.equal(inner2.latex(), '3+1');
  5246. assert.equal(outer.latex(), '\\frac{x_0+x_1+x_2+x_3}{3+1}');
  5247. });
  5248. test('optional inner field name', function() {
  5249. outer.latex('\\MathQuillMathField[mantissa]{}\\cdot\\MathQuillMathField[base]{}^{\\MathQuillMathField[exp]{}}');
  5250. assert.equal(outer.innerFields.length, 3);
  5251. var mantissa = outer.innerFields.mantissa;
  5252. var base = outer.innerFields.base;
  5253. var exp = outer.innerFields.exp;
  5254. assert.equal(mantissa, outer.innerFields[0]);
  5255. assert.equal(base, outer.innerFields[1]);
  5256. assert.equal(exp, outer.innerFields[2]);
  5257. mantissa.latex('1.2345');
  5258. base.latex('10');
  5259. exp.latex('8');
  5260. assert.equal(outer.latex(), '1.2345\\cdot10^8');
  5261. });
  5262. test('separate API object', function() {
  5263. var outer2 = MQ(outer.el());
  5264. assert.equal(outer2.innerFields.length, 2);
  5265. assert.equal(outer2.innerFields[0].id, inner1.id);
  5266. assert.equal(outer2.innerFields[1].id, inner2.id);
  5267. });
  5268. });
  5269. suite('error handling', function() {
  5270. var mq;
  5271. setup(function() {
  5272. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5273. });
  5274. teardown(function() {
  5275. $(mq.el()).remove();
  5276. });
  5277. function testCantParse(title /*, latex...*/) {
  5278. var latex = [].slice.call(arguments, 1);
  5279. test(title, function() {
  5280. for (var i = 0; i < latex.length; i += 1) {
  5281. mq.latex(latex[i]);
  5282. assert.equal(mq.latex(), '', "shouldn\'t parse '"+latex[i]+"'");
  5283. }
  5284. });
  5285. }
  5286. testCantParse('missing blocks', '\\frac', '\\sqrt', '^', '_');
  5287. testCantParse('unmatched close brace', '}', ' 1 + 2 } ', '1 - {2 + 3} }', '\\sqrt{ x }} + \\sqrt{y}');
  5288. testCantParse('unmatched open brace', '{', '1 * { 2 + 3', '\\frac{ \\sqrt x }{{ \\sqrt y}');
  5289. testCantParse('unmatched \\left/\\right', '\\left ( 1 + 2 )', ' [ 1, 2 \\right ]');
  5290. });
  5291. });
  5292. suite('parser', function() {
  5293. var string = Parser.string;
  5294. var regex = Parser.regex;
  5295. var letter = Parser.letter;
  5296. var digit = Parser.digit;
  5297. var any = Parser.any;
  5298. var optWhitespace = Parser.optWhitespace;
  5299. var eof = Parser.eof;
  5300. var succeed = Parser.succeed;
  5301. var all = Parser.all;
  5302. test('Parser.string', function() {
  5303. var parser = string('x');
  5304. assert.equal(parser.parse('x'), 'x');
  5305. assert.throws(function() { parser.parse('y') })
  5306. });
  5307. test('Parser.regex', function() {
  5308. var parser = regex(/^[0-9]/);
  5309. assert.equal(parser.parse('1'), '1');
  5310. assert.equal(parser.parse('4'), '4');
  5311. assert.throws(function() { parser.parse('x'); });
  5312. assert.throws(function() { regex(/./) }, 'must be anchored');
  5313. });
  5314. suite('then', function() {
  5315. test('with a parser, uses the last return value', function() {
  5316. var parser = string('x').then(string('y'));
  5317. assert.equal(parser.parse('xy'), 'y');
  5318. assert.throws(function() { parser.parse('y'); });
  5319. assert.throws(function() { parser.parse('xz'); });
  5320. });
  5321. test('asserts that a parser is returned', function() {
  5322. var parser1 = letter.then(function() { return 'not a parser' });
  5323. assert.throws(function() { parser1.parse('x'); });
  5324. var parser2 = letter.then('x');
  5325. assert.throws(function() { letter.parse('xx'); });
  5326. });
  5327. test('with a function that returns a parser, continues with that parser', function() {
  5328. var piped;
  5329. var parser = string('x').then(function(x) {
  5330. piped = x;
  5331. return string('y');
  5332. });
  5333. assert.equal(parser.parse('xy'), 'y');
  5334. assert.equal(piped, 'x');
  5335. assert.throws(function() { parser.parse('x'); });
  5336. });
  5337. });
  5338. suite('map', function() {
  5339. test('with a function, pipes the value in and uses that return value', function() {
  5340. var piped;
  5341. var parser = string('x').map(function(x) {
  5342. piped = x;
  5343. return 'y';
  5344. });
  5345. assert.equal(parser.parse('x'), 'y')
  5346. assert.equal(piped, 'x');
  5347. });
  5348. });
  5349. suite('result', function() {
  5350. test('returns a constant result', function() {
  5351. var myResult = 1;
  5352. var oneParser = string('x').result(1);
  5353. assert.equal(oneParser.parse('x'), 1);
  5354. var myFn = function() {};
  5355. var fnParser = string('x').result(myFn);
  5356. assert.equal(fnParser.parse('x'), myFn);
  5357. });
  5358. });
  5359. suite('skip', function() {
  5360. test('uses the previous return value', function() {
  5361. var parser = string('x').skip(string('y'));
  5362. assert.equal(parser.parse('xy'), 'x');
  5363. assert.throws(function() { parser.parse('x'); });
  5364. });
  5365. });
  5366. suite('or', function() {
  5367. test('two parsers', function() {
  5368. var parser = string('x').or(string('y'));
  5369. assert.equal(parser.parse('x'), 'x');
  5370. assert.equal(parser.parse('y'), 'y');
  5371. assert.throws(function() { parser.parse('z') });
  5372. });
  5373. test('with then', function() {
  5374. var parser = string('\\')
  5375. .then(function() {
  5376. return string('y')
  5377. }).or(string('z'));
  5378. assert.equal(parser.parse('\\y'), 'y');
  5379. assert.equal(parser.parse('z'), 'z');
  5380. assert.throws(function() { parser.parse('\\z') });
  5381. });
  5382. });
  5383. function assertEqualArray(arr1, arr2) {
  5384. assert.equal(arr1.join(), arr2.join());
  5385. }
  5386. suite('many', function() {
  5387. test('simple case', function() {
  5388. var letters = letter.many();
  5389. assertEqualArray(letters.parse('x'), ['x']);
  5390. assertEqualArray(letters.parse('xyz'), ['x','y','z']);
  5391. assertEqualArray(letters.parse(''), []);
  5392. assert.throws(function() { letters.parse('1'); });
  5393. assert.throws(function() { letters.parse('xyz1'); });
  5394. });
  5395. test('followed by then', function() {
  5396. var parser = string('x').many().then(string('y'));
  5397. assert.equal(parser.parse('y'), 'y');
  5398. assert.equal(parser.parse('xy'), 'y');
  5399. assert.equal(parser.parse('xxxxxy'), 'y');
  5400. });
  5401. });
  5402. suite('times', function() {
  5403. test('zero case', function() {
  5404. var zeroLetters = letter.times(0);
  5405. assertEqualArray(zeroLetters.parse(''), []);
  5406. assert.throws(function() { zeroLetters.parse('x'); });
  5407. });
  5408. test('nonzero case', function() {
  5409. var threeLetters = letter.times(3);
  5410. assertEqualArray(threeLetters.parse('xyz'), ['x', 'y', 'z']);
  5411. assert.throws(function() { threeLetters.parse('xy'); });
  5412. assert.throws(function() { threeLetters.parse('xyzw'); });
  5413. var thenDigit = threeLetters.then(digit);
  5414. assert.equal(thenDigit.parse('xyz1'), '1');
  5415. assert.throws(function() { thenDigit.parse('xy1'); });
  5416. assert.throws(function() { thenDigit.parse('xyz'); });
  5417. assert.throws(function() { thenDigit.parse('xyzw'); });
  5418. });
  5419. test('with a min and max', function() {
  5420. var someLetters = letter.times(2, 4);
  5421. assertEqualArray(someLetters.parse('xy'), ['x', 'y']);
  5422. assertEqualArray(someLetters.parse('xyz'), ['x', 'y', 'z']);
  5423. assertEqualArray(someLetters.parse('xyzw'), ['x', 'y', 'z', 'w']);
  5424. assert.throws(function() { someLetters.parse('xyzwv'); });
  5425. assert.throws(function() { someLetters.parse('x'); });
  5426. var thenDigit = someLetters.then(digit);
  5427. assert.equal(thenDigit.parse('xy1'), '1');
  5428. assert.equal(thenDigit.parse('xyz1'), '1');
  5429. assert.equal(thenDigit.parse('xyzw1'), '1');
  5430. assert.throws(function() { thenDigit.parse('xy'); });
  5431. assert.throws(function() { thenDigit.parse('xyzw'); });
  5432. assert.throws(function() { thenDigit.parse('xyzwv1'); });
  5433. assert.throws(function() { thenDigit.parse('x1'); });
  5434. });
  5435. test('atLeast', function() {
  5436. var atLeastTwo = letter.atLeast(2);
  5437. assertEqualArray(atLeastTwo.parse('xy'), ['x', 'y']);
  5438. assertEqualArray(atLeastTwo.parse('xyzw'), ['x', 'y', 'z', 'w']);
  5439. assert.throws(function() { atLeastTwo.parse('x'); });
  5440. });
  5441. });
  5442. suite('fail', function() {
  5443. var fail = Parser.fail;
  5444. var succeed = Parser.succeed;
  5445. test('use Parser.fail to fail dynamically', function() {
  5446. var parser = any.then(function(ch) {
  5447. return fail('character '+ch+' not allowed');
  5448. }).or(string('x'));
  5449. assert.throws(function() { parser.parse('y'); });
  5450. assert.equal(parser.parse('x'), 'x');
  5451. });
  5452. test('use Parser.succeed or Parser.fail to branch conditionally', function() {
  5453. var allowedOperator;
  5454. var parser =
  5455. string('x')
  5456. .then(string('+').or(string('*')))
  5457. .then(function(operator) {
  5458. if (operator === allowedOperator) return succeed(operator);
  5459. else return fail('expected '+allowedOperator);
  5460. })
  5461. .skip(string('y'))
  5462. ;
  5463. allowedOperator = '+';
  5464. assert.equal(parser.parse('x+y'), '+');
  5465. assert.throws(function() { parser.parse('x*y'); });
  5466. allowedOperator = '*';
  5467. assert.equal(parser.parse('x*y'), '*');
  5468. assert.throws(function() { parser.parse('x+y'); });
  5469. });
  5470. });
  5471. test('eof', function() {
  5472. var parser = optWhitespace.skip(eof).or(all.result('default'));
  5473. assert.equal(parser.parse(' '), ' ')
  5474. assert.equal(parser.parse('x'), 'default');
  5475. });
  5476. });
  5477. suite('Public API', function() {
  5478. suite('global functions', function() {
  5479. test('null', function() {
  5480. assert.equal(MQ(), null);
  5481. assert.equal(MQ(0), null);
  5482. assert.equal(MQ('<span/>'), null);
  5483. assert.equal(MQ($('<span/>')[0]), null);
  5484. assert.equal(MQ.MathField(), null);
  5485. assert.equal(MQ.MathField(0), null);
  5486. assert.equal(MQ.MathField('<span/>'), null);
  5487. });
  5488. test('MQ.MathField()', function() {
  5489. var el = $('<span>x^2</span>');
  5490. var mathField = MQ.MathField(el[0]);
  5491. assert.ok(mathField instanceof MQ.MathField);
  5492. assert.ok(mathField instanceof MQ.EditableField);
  5493. assert.ok(mathField instanceof MQ);
  5494. assert.ok(mathField instanceof MathQuill);
  5495. });
  5496. test('interface versioning isolates prototype chain', function() {
  5497. var mathFieldSpan = $('<span/>')[0];
  5498. var mathField = MQ.MathField(mathFieldSpan);
  5499. var MQ1 = MathQuill.getInterface(1);
  5500. assert.ok(!(mathField instanceof MQ1.MathField));
  5501. assert.ok(!(mathField instanceof MQ1.EditableField));
  5502. assert.ok(!(mathField instanceof MQ1));
  5503. });
  5504. test('identity of API object returned by MQ()', function() {
  5505. var mathFieldSpan = $('<span/>')[0];
  5506. var mathField = MQ.MathField(mathFieldSpan);
  5507. assert.ok(MQ(mathFieldSpan) !== mathField);
  5508. assert.equal(MQ(mathFieldSpan).id, mathField.id);
  5509. assert.equal(MQ(mathFieldSpan).id, MQ(mathFieldSpan).id);
  5510. assert.equal(MQ(mathFieldSpan).data, mathField.data);
  5511. assert.equal(MQ(mathFieldSpan).data, MQ(mathFieldSpan).data);
  5512. });
  5513. test('blurred when created', function() {
  5514. var el = $('<span/>');
  5515. MQ.MathField(el[0]);
  5516. var rootBlock = el.find('.mq-root-block');
  5517. assert.ok(rootBlock.hasClass('mq-empty'));
  5518. assert.ok(!rootBlock.hasClass('mq-hasCursor'));
  5519. });
  5520. });
  5521. suite('mathquill-basic', function() {
  5522. var mq;
  5523. setup(function() {
  5524. mq = MQBasic.MathField($('<span></span>').appendTo('#mock')[0]);
  5525. });
  5526. teardown(function() {
  5527. $(mq.el()).remove();
  5528. });
  5529. test('typing \\', function() {
  5530. mq.typedText('\\');
  5531. assert.equal(mq.latex(), '\\backslash');
  5532. });
  5533. test('typing $', function() {
  5534. mq.typedText('$');
  5535. assert.equal(mq.latex(), '\\$');
  5536. });
  5537. test('parsing of advanced symbols', function() {
  5538. mq.latex('\\oplus');
  5539. assert.equal(mq.latex(), ''); // TODO: better LaTeX parse error behavior
  5540. });
  5541. });
  5542. suite('basic API methods', function() {
  5543. var mq;
  5544. setup(function() {
  5545. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5546. });
  5547. teardown(function() {
  5548. $(mq.el()).remove();
  5549. });
  5550. test('.revert()', function() {
  5551. var mq = MQ.MathField($('<span>some <code>HTML</code></span>')[0]);
  5552. assert.equal(mq.revert().html(), 'some <code>HTML</code>');
  5553. });
  5554. test('select, clearSelection', function() {
  5555. mq.latex('n+\\frac{n}{2}');
  5556. assert.ok(!mq.__controller.cursor.selection);
  5557. mq.select();
  5558. assert.equal(mq.__controller.cursor.selection.join('latex'), 'n+\\frac{n}{2}');
  5559. mq.clearSelection();
  5560. assert.ok(!mq.__controller.cursor.selection);
  5561. });
  5562. test('latex while there\'s a selection', function() {
  5563. mq.latex('a');
  5564. assert.equal(mq.latex(), 'a');
  5565. mq.select();
  5566. assert.equal(mq.__controller.cursor.selection.join('latex'), 'a');
  5567. mq.latex('b');
  5568. assert.equal(mq.latex(), 'b');
  5569. mq.typedText('c');
  5570. assert.equal(mq.latex(), 'bc');
  5571. });
  5572. test('.html() trivial case', function() {
  5573. mq.latex('x+y');
  5574. assert.equal(mq.html(), '<var>x</var><span class="mq-binary-operator">+</span><var>y</var>');
  5575. });
  5576. test('.text() with incomplete commands', function() {
  5577. assert.equal(mq.text(), '');
  5578. mq.typedText('\\');
  5579. assert.equal(mq.text(), '\\');
  5580. mq.typedText('s');
  5581. assert.equal(mq.text(), '\\s');
  5582. mq.typedText('qrt');
  5583. assert.equal(mq.text(), '\\sqrt');
  5584. });
  5585. test('.text() with complete commands', function() {
  5586. mq.latex('\\sqrt{}');
  5587. assert.equal(mq.text(), 'sqrt()');
  5588. mq.latex('\\nthroot[]{}');
  5589. assert.equal(mq.text(), 'sqrt[]()');
  5590. mq.latex('\\frac{}{}');
  5591. assert.equal(mq.text(), '()/()');
  5592. mq.latex('\\frac{3}{5}');
  5593. assert.equal(mq.text(), '(3)/(5)');
  5594. mq.latex('\\frac{3+2}{5-1}');
  5595. assert.equal(mq.text(), '(3+2)/(5-1)');
  5596. mq.latex('\\div');
  5597. assert.equal(mq.text(), '[/]');
  5598. mq.latex('^{}');
  5599. assert.equal(mq.text(), '^');
  5600. mq.latex('3^{4}');
  5601. assert.equal(mq.text(), '3^4');
  5602. mq.latex('3x+\\ 4');
  5603. assert.equal(mq.text(), '3*x+ 4');
  5604. mq.latex('x^2');
  5605. assert.equal(mq.text(), 'x^2');
  5606. mq.latex('');
  5607. mq.typedText('*2*3***4');
  5608. assert.equal(mq.text(), '*2*3***4');
  5609. });
  5610. test('.moveToDirEnd(dir)', function() {
  5611. mq.latex('a x^2 + b x + c = 0');
  5612. assert.equal(mq.__controller.cursor[L].ctrlSeq, '0');
  5613. assert.equal(mq.__controller.cursor[R], 0);
  5614. mq.moveToLeftEnd();
  5615. assert.equal(mq.__controller.cursor[L], 0);
  5616. assert.equal(mq.__controller.cursor[R].ctrlSeq, 'a');
  5617. mq.moveToRightEnd();
  5618. assert.equal(mq.__controller.cursor[L].ctrlSeq, '0');
  5619. assert.equal(mq.__controller.cursor[R], 0);
  5620. });
  5621. });
  5622. test('edit handler interface versioning', function() {
  5623. var count = 0;
  5624. // interface version 2 (latest)
  5625. var mq2 = MQ.MathField($('<span></span>').appendTo('#mock')[0], {
  5626. handlers: {
  5627. edit: function(_mq) {
  5628. assert.equal(mq2.id, _mq.id);
  5629. count += 1;
  5630. }
  5631. }
  5632. });
  5633. assert.equal(count, 0);
  5634. mq2.latex('x^2');
  5635. assert.equal(count, 2); // sigh, once for postOrder and once for bubble
  5636. count = 0;
  5637. // interface version 1
  5638. var MQ1 = MathQuill.getInterface(1);
  5639. var mq1 = MQ1.MathField($('<span></span>').appendTo('#mock')[0], {
  5640. handlers: {
  5641. edit: function(_mq) {
  5642. if (count <= 2) assert.equal(mq1, undefined);
  5643. else assert.equal(mq1.id, _mq.id);
  5644. count += 1;
  5645. }
  5646. }
  5647. });
  5648. assert.equal(count, 2);
  5649. });
  5650. suite('*OutOf handlers', function() {
  5651. testHandlers('MQ.MathField() constructor', function(options) {
  5652. return MQ.MathField($('<span></span>').appendTo('#mock')[0], options);
  5653. });
  5654. testHandlers('MQ.MathField::config()', function(options) {
  5655. return MQ.MathField($('<span></span>').appendTo('#mock')[0]).config(options);
  5656. });
  5657. testHandlers('.config() on \\MathQuillMathField{} in a MQ.StaticMath', function(options) {
  5658. return MQ.MathField($('<span></span>').appendTo('#mock')[0]).config(options);
  5659. });
  5660. suite('global MQ.config()', function() {
  5661. testHandlers('a MQ.MathField', function(options) {
  5662. MQ.config(options);
  5663. return MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5664. });
  5665. testHandlers('\\MathQuillMathField{} in a MQ.StaticMath', function(options) {
  5666. MQ.config(options);
  5667. return MQ.StaticMath($('<span>\\MathQuillMathField{}</span>').appendTo('#mock')[0]).innerFields[0];
  5668. });
  5669. teardown(function() {
  5670. MQ.config({ handlers: undefined });
  5671. });
  5672. });
  5673. function testHandlers(title, mathFieldMaker) {
  5674. test(title, function() {
  5675. var enterCounter = 0, upCounter = 0, moveCounter = 0, deleteCounter = 0,
  5676. dir = null;
  5677. var mq = mathFieldMaker({
  5678. handlers: {
  5679. enter: function(_mq) {
  5680. assert.equal(arguments.length, 1);
  5681. assert.equal(_mq.id, mq.id);
  5682. enterCounter += 1;
  5683. },
  5684. upOutOf: function(_mq) {
  5685. assert.equal(arguments.length, 1);
  5686. assert.equal(_mq.id, mq.id);
  5687. upCounter += 1;
  5688. },
  5689. moveOutOf: function(_dir, _mq) {
  5690. assert.equal(arguments.length, 2);
  5691. assert.equal(_mq.id, mq.id);
  5692. dir = _dir;
  5693. moveCounter += 1;
  5694. },
  5695. deleteOutOf: function(_dir, _mq) {
  5696. assert.equal(arguments.length, 2);
  5697. assert.equal(_mq.id, mq.id);
  5698. dir = _dir;
  5699. deleteCounter += 1;
  5700. }
  5701. }
  5702. });
  5703. mq.latex('n+\\frac{n}{2}'); // starts at right edge
  5704. assert.equal(moveCounter, 0);
  5705. mq.typedText('\n'); // nothing happens
  5706. assert.equal(enterCounter, 1);
  5707. mq.keystroke('Right'); // stay at right edge
  5708. assert.equal(moveCounter, 1);
  5709. assert.equal(dir, R);
  5710. mq.keystroke('Right'); // stay at right edge
  5711. assert.equal(moveCounter, 2);
  5712. assert.equal(dir, R);
  5713. mq.keystroke('Left'); // right edge of denominator
  5714. assert.equal(moveCounter, 2);
  5715. assert.equal(upCounter, 0);
  5716. mq.keystroke('Up'); // right edge of numerator
  5717. assert.equal(moveCounter, 2);
  5718. assert.equal(upCounter, 0);
  5719. mq.keystroke('Up'); // stays at right edge of numerator
  5720. assert.equal(upCounter, 1);
  5721. mq.keystroke('Up'); // stays at right edge of numerator
  5722. assert.equal(upCounter, 2);
  5723. // go to left edge
  5724. mq.keystroke('Left').keystroke('Left').keystroke('Left').keystroke('Left');
  5725. assert.equal(moveCounter, 2);
  5726. mq.keystroke('Left'); // stays at left edge
  5727. assert.equal(moveCounter, 3);
  5728. assert.equal(dir, L);
  5729. assert.equal(deleteCounter, 0);
  5730. mq.keystroke('Backspace'); // stays at left edge
  5731. assert.equal(deleteCounter, 1);
  5732. assert.equal(dir, L);
  5733. mq.keystroke('Backspace'); // stays at left edge
  5734. assert.equal(deleteCounter, 2);
  5735. assert.equal(dir, L);
  5736. mq.keystroke('Left'); // stays at left edge
  5737. assert.equal(moveCounter, 4);
  5738. assert.equal(dir, L);
  5739. $('#mock').empty();
  5740. });
  5741. }
  5742. });
  5743. suite('.cmd(...)', function() {
  5744. var mq;
  5745. setup(function() {
  5746. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5747. });
  5748. teardown(function() {
  5749. $(mq.el()).remove();
  5750. });
  5751. test('basic', function() {
  5752. mq.cmd('x');
  5753. assert.equal(mq.latex(), 'x');
  5754. mq.cmd('y');
  5755. assert.equal(mq.latex(), 'xy');
  5756. mq.cmd('^');
  5757. assert.equal(mq.latex(), 'xy^{ }');
  5758. mq.cmd('2');
  5759. assert.equal(mq.latex(), 'xy^2');
  5760. mq.keystroke('Right Shift-Left Shift-Left Shift-Left').cmd('\\sqrt');
  5761. assert.equal(mq.latex(), '\\sqrt{xy^2}');
  5762. mq.typedText('*2**');
  5763. assert.equal(mq.latex(), '\\sqrt{xy^2\\cdot2\\cdot\\cdot}');
  5764. });
  5765. test('backslash commands are passed their name', function() {
  5766. mq.cmd('\\alpha');
  5767. assert.equal(mq.latex(), '\\alpha');
  5768. });
  5769. test('replaces selection', function() {
  5770. mq.typedText('49').select().cmd('\\sqrt');
  5771. assert.equal(mq.latex(), '\\sqrt{49}');
  5772. });
  5773. test('operator name', function() {
  5774. mq.cmd('\\sin');
  5775. assert.equal(mq.latex(), '\\sin');
  5776. });
  5777. test('nonexistent LaTeX command is noop', function() {
  5778. mq.typedText('49').select().cmd('\\asdf').cmd('\\sqrt');
  5779. assert.equal(mq.latex(), '\\sqrt{49}');
  5780. });
  5781. test('overflow triggers automatic horizontal scroll', function(done) {
  5782. var mqEl = mq.el();
  5783. var rootEl = mq.__controller.root.jQ[0];
  5784. var cursor = mq.__controller.cursor;
  5785. $(mqEl).width(10);
  5786. var previousScrollLeft = rootEl.scrollLeft;
  5787. mq.cmd("\\alpha");
  5788. setTimeout(afterScroll, 150);
  5789. function afterScroll() {
  5790. cursor.show();
  5791. try {
  5792. assert.ok(rootEl.scrollLeft > previousScrollLeft, "scrolls on cmd");
  5793. assert.ok(mqEl.getBoundingClientRect().right > cursor.jQ[0].getBoundingClientRect().right,
  5794. "cursor right end is inside the field");
  5795. }
  5796. catch(error) {
  5797. done(error);
  5798. return;
  5799. }
  5800. done();
  5801. }
  5802. });
  5803. });
  5804. suite('spaceBehavesLikeTab', function() {
  5805. var mq, rootBlock, cursor;
  5806. test('space behaves like tab with default opts', function() {
  5807. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5808. rootBlock = mq.__controller.root;
  5809. cursor = mq.__controller.cursor;
  5810. mq.latex('\\sqrt{x}');
  5811. mq.keystroke('Left');
  5812. mq.keystroke('Spacebar');
  5813. mq.typedText(' ');
  5814. assert.equal(cursor[L].ctrlSeq, '\\ ', 'left of the cursor is ' + cursor[L].ctrlSeq);
  5815. assert.equal(cursor[R], 0, 'right of the cursor is ' + cursor[R]);
  5816. mq.keystroke('Backspace');
  5817. mq.keystroke('Shift-Spacebar');
  5818. mq.typedText(' ');
  5819. assert.equal(cursor[L].ctrlSeq, '\\ ', 'left of the cursor is ' + cursor[L].ctrlSeq);
  5820. assert.equal(cursor[R], 0, 'right of the cursor is ' + cursor[R]);
  5821. $(mq.el()).remove();
  5822. });
  5823. test('space behaves like tab when spaceBehavesLikeTab is true', function() {
  5824. var opts = { 'spaceBehavesLikeTab': true };
  5825. mq = MQ.MathField( $('<span></span>').appendTo('#mock')[0], opts)
  5826. rootBlock = mq.__controller.root;
  5827. cursor = mq.__controller.cursor;
  5828. mq.latex('\\sqrt{x}');
  5829. mq.keystroke('Left');
  5830. mq.keystroke('Spacebar');
  5831. assert.equal(cursor[L].parent, rootBlock, 'parent of the cursor is ' + cursor[L].ctrlSeq);
  5832. assert.equal(cursor[R], 0, 'right cursor is ' + cursor[R]);
  5833. mq.keystroke('Left');
  5834. mq.keystroke('Shift-Spacebar');
  5835. assert.equal(cursor[L], 0, 'left cursor is ' + cursor[L]);
  5836. assert.equal(cursor[R], rootBlock.ends[L], 'parent of rootBlock is ' + cursor[R]);
  5837. $(mq.el()).remove();
  5838. });
  5839. test('space behaves like tab when globally set to true', function() {
  5840. MQ.config({ spaceBehavesLikeTab: true });
  5841. mq = MQ.MathField( $('<span></span>').appendTo('#mock')[0]);
  5842. rootBlock = mq.__controller.root;
  5843. cursor = mq.__controller.cursor;
  5844. mq.latex('\\sqrt{x}');
  5845. mq.keystroke('Left');
  5846. mq.keystroke('Spacebar');
  5847. assert.equal(cursor.parent, rootBlock, 'cursor in root block');
  5848. assert.equal(cursor[R], 0, 'cursor at end of block');
  5849. $(mq.el()).remove();
  5850. });
  5851. });
  5852. suite('statelessClipboard option', function() {
  5853. suite('default', function() {
  5854. var mq, textarea;
  5855. setup(function() {
  5856. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5857. textarea = $(mq.el()).find('textarea');;
  5858. });
  5859. teardown(function() {
  5860. $(mq.el()).remove();
  5861. });
  5862. function assertPaste(paste, latex) {
  5863. if (arguments.length < 2) latex = paste;
  5864. mq.latex('');
  5865. textarea.trigger('paste').val(paste).trigger('input');
  5866. assert.equal(mq.latex(), latex);
  5867. }
  5868. test('numbers and letters', function() {
  5869. assertPaste('123xyz');
  5870. });
  5871. test('a sentence', function() {
  5872. assertPaste('Lorem ipsum is a placeholder text commonly used to '
  5873. + 'demonstrate the graphical elements of a document or '
  5874. + 'visual presentation.',
  5875. 'Loremipsumisaplaceholdertextcommonlyusedtodemonstrate'
  5876. + 'thegraphicalelementsofadocumentorvisualpresentation.');
  5877. });
  5878. test('actual LaTeX', function() {
  5879. assertPaste('a_nx^n+a_{n+1}x^{n+1}');
  5880. assertPaste('\\frac{1}{2\\sqrt{x}}');
  5881. });
  5882. test('\\text{...}', function() {
  5883. assertPaste('\\text{lol}');
  5884. assertPaste('1+\\text{lol}+2');
  5885. assertPaste('\\frac{\\text{apples}}{\\text{oranges}}');
  5886. });
  5887. test('selection', function(done) {
  5888. mq.latex('x^2').select();
  5889. setTimeout(function() {
  5890. assert.equal(textarea.val(), 'x^2');
  5891. done();
  5892. });
  5893. });
  5894. });
  5895. suite('statelessClipboard set to true', function() {
  5896. var mq, textarea;
  5897. setup(function() {
  5898. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0],
  5899. { statelessClipboard: true });
  5900. textarea = $(mq.el()).find('textarea');;
  5901. });
  5902. teardown(function() {
  5903. $(mq.el()).remove();
  5904. });
  5905. function assertPaste(paste, latex) {
  5906. if (arguments.length < 2) latex = paste;
  5907. mq.latex('');
  5908. textarea.trigger('paste').val(paste).trigger('input');
  5909. assert.equal(mq.latex(), latex);
  5910. }
  5911. test('numbers and letters', function() {
  5912. assertPaste('123xyz', '\\text{123xyz}');
  5913. });
  5914. test('a sentence', function() {
  5915. assertPaste('Lorem ipsum is a placeholder text commonly used to '
  5916. + 'demonstrate the graphical elements of a document or '
  5917. + 'visual presentation.',
  5918. '\\text{Lorem ipsum is a placeholder text commonly used to '
  5919. + 'demonstrate the graphical elements of a document or '
  5920. + 'visual presentation.}');
  5921. });
  5922. test('backslashes', function() {
  5923. assertPaste('something \\pi something \\asdf',
  5924. '\\text{something \\pi something \\asdf}');
  5925. });
  5926. // TODO: braces (currently broken)
  5927. test('actual math LaTeX wrapped in dollar signs', function() {
  5928. assertPaste('$a_nx^n+a_{n+1}x^{n+1}$', 'a_nx^n+a_{n+1}x^{n+1}');
  5929. assertPaste('$\\frac{1}{2\\sqrt{x}}$', '\\frac{1}{2\\sqrt{x}}');
  5930. });
  5931. test('selection', function(done) {
  5932. mq.latex('x^2').select();
  5933. setTimeout(function() {
  5934. assert.equal(textarea.val(), '$x^2$');
  5935. done();
  5936. });
  5937. });
  5938. });
  5939. });
  5940. suite('leftRightIntoCmdGoes: "up"/"down"', function() {
  5941. test('"up" or "down" required', function() {
  5942. assert.throws(function() {
  5943. MQ.MathField($('<span></span>')[0], { leftRightIntoCmdGoes: 1 });
  5944. });
  5945. });
  5946. suite('default', function() {
  5947. var mq;
  5948. setup(function() {
  5949. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  5950. });
  5951. teardown(function() {
  5952. $(mq.el()).remove();
  5953. });
  5954. test('fractions', function() {
  5955. mq.latex('\\frac{1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  5956. assert.equal(mq.latex(), '\\frac{1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  5957. mq.moveToLeftEnd().typedText('a');
  5958. assert.equal(mq.latex(), 'a\\frac{1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  5959. mq.keystroke('Right').typedText('b');
  5960. assert.equal(mq.latex(), 'a\\frac{b1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  5961. mq.keystroke('Right Right').typedText('c');
  5962. assert.equal(mq.latex(), 'a\\frac{b1}{cx}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  5963. mq.keystroke('Right Right').typedText('d');
  5964. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  5965. mq.keystroke('Right Right').typedText('e');
  5966. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{1}{2}}{\\frac{3}{4}}');
  5967. mq.keystroke('Right').typedText('f');
  5968. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{2}}{\\frac{3}{4}}');
  5969. mq.keystroke('Right Right').typedText('g');
  5970. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}}{\\frac{3}{4}}');
  5971. mq.keystroke('Right Right').typedText('h');
  5972. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}h}{\\frac{3}{4}}');
  5973. mq.keystroke('Right').typedText('i');
  5974. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}h}{i\\frac{3}{4}}');
  5975. mq.keystroke('Right').typedText('j');
  5976. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}h}{i\\frac{j3}{4}}');
  5977. mq.keystroke('Right Right').typedText('k');
  5978. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}h}{i\\frac{j3}{k4}}');
  5979. mq.keystroke('Right Right').typedText('l');
  5980. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}h}{i\\frac{j3}{k4}l}');
  5981. mq.keystroke('Right').typedText('m');
  5982. assert.equal(mq.latex(), 'a\\frac{b1}{cx}d+\\frac{e\\frac{f1}{g2}h}{i\\frac{j3}{k4}l}m');
  5983. });
  5984. test('supsub', function() {
  5985. mq.latex('x_a+y^b+z_a^b+w');
  5986. assert.equal(mq.latex(), 'x_a+y^b+z_a^b+w');
  5987. mq.moveToLeftEnd().typedText('1');
  5988. assert.equal(mq.latex(), '1x_a+y^b+z_a^b+w');
  5989. mq.keystroke('Right Right').typedText('2');
  5990. assert.equal(mq.latex(), '1x_{2a}+y^b+z_a^b+w');
  5991. mq.keystroke('Right Right').typedText('3');
  5992. assert.equal(mq.latex(), '1x_{2a}3+y^b+z_a^b+w');
  5993. mq.keystroke('Right Right Right').typedText('4');
  5994. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}+z_a^b+w');
  5995. mq.keystroke('Right Right').typedText('5');
  5996. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_a^b+w');
  5997. mq.keystroke('Right Right Right').typedText('6');
  5998. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_{6a}^b+w');
  5999. mq.keystroke('Right Right').typedText('7');
  6000. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_{6a}^{7b}+w');
  6001. mq.keystroke('Right Right').typedText('8');
  6002. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_{6a}^{7b}8+w');
  6003. });
  6004. test('nthroot', function() {
  6005. mq.latex('\\sqrt[n]{x}');
  6006. assert.equal(mq.latex(), '\\sqrt[n]{x}');
  6007. mq.moveToLeftEnd().typedText('1');
  6008. assert.equal(mq.latex(), '1\\sqrt[n]{x}');
  6009. mq.keystroke('Right').typedText('2');
  6010. assert.equal(mq.latex(), '1\\sqrt[2n]{x}');
  6011. mq.keystroke('Right Right').typedText('3');
  6012. assert.equal(mq.latex(), '1\\sqrt[2n]{3x}');
  6013. mq.keystroke('Right Right').typedText('4');
  6014. assert.equal(mq.latex(), '1\\sqrt[2n]{3x}4');
  6015. });
  6016. });
  6017. suite('"up"', function() {
  6018. var mq;
  6019. setup(function() {
  6020. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0],
  6021. { leftRightIntoCmdGoes: 'up' });
  6022. });
  6023. teardown(function() {
  6024. $(mq.el()).remove();
  6025. });
  6026. test('fractions', function() {
  6027. mq.latex('\\frac{1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  6028. assert.equal(mq.latex(), '\\frac{1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  6029. mq.moveToLeftEnd().typedText('a');
  6030. assert.equal(mq.latex(), 'a\\frac{1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  6031. mq.keystroke('Right').typedText('b');
  6032. assert.equal(mq.latex(), 'a\\frac{b1}{x}+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  6033. mq.keystroke('Right Right').typedText('c');
  6034. assert.equal(mq.latex(), 'a\\frac{b1}{x}c+\\frac{\\frac{1}{2}}{\\frac{3}{4}}');
  6035. mq.keystroke('Right Right').typedText('d');
  6036. assert.equal(mq.latex(), 'a\\frac{b1}{x}c+\\frac{d\\frac{1}{2}}{\\frac{3}{4}}');
  6037. mq.keystroke('Right').typedText('e');
  6038. assert.equal(mq.latex(), 'a\\frac{b1}{x}c+\\frac{d\\frac{e1}{2}}{\\frac{3}{4}}');
  6039. mq.keystroke('Right Right').typedText('f');
  6040. assert.equal(mq.latex(), 'a\\frac{b1}{x}c+\\frac{d\\frac{e1}{2}f}{\\frac{3}{4}}');
  6041. mq.keystroke('Right').typedText('g');
  6042. assert.equal(mq.latex(), 'a\\frac{b1}{x}c+\\frac{d\\frac{e1}{2}f}{\\frac{3}{4}}g');
  6043. });
  6044. test('supsub', function() {
  6045. mq.latex('x_a+y^b+z_a^b+w');
  6046. assert.equal(mq.latex(), 'x_a+y^b+z_a^b+w');
  6047. mq.moveToLeftEnd().typedText('1');
  6048. assert.equal(mq.latex(), '1x_a+y^b+z_a^b+w');
  6049. mq.keystroke('Right Right').typedText('2');
  6050. assert.equal(mq.latex(), '1x_{2a}+y^b+z_a^b+w');
  6051. mq.keystroke('Right Right').typedText('3');
  6052. assert.equal(mq.latex(), '1x_{2a}3+y^b+z_a^b+w');
  6053. mq.keystroke('Right Right Right').typedText('4');
  6054. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}+z_a^b+w');
  6055. mq.keystroke('Right Right').typedText('5');
  6056. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_a^b+w');
  6057. mq.keystroke('Right Right Right').typedText('6');
  6058. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_a^{6b}+w');
  6059. mq.keystroke('Right Right').typedText('7');
  6060. assert.equal(mq.latex(), '1x_{2a}3+y^{4b}5+z_a^{6b}7+w');
  6061. });
  6062. test('nthroot', function() {
  6063. mq.latex('\\sqrt[n]{x}');
  6064. assert.equal(mq.latex(), '\\sqrt[n]{x}');
  6065. mq.moveToLeftEnd().typedText('1');
  6066. assert.equal(mq.latex(), '1\\sqrt[n]{x}');
  6067. mq.keystroke('Right').typedText('2');
  6068. assert.equal(mq.latex(), '1\\sqrt[2n]{x}');
  6069. mq.keystroke('Right Right').typedText('3');
  6070. assert.equal(mq.latex(), '1\\sqrt[2n]{3x}');
  6071. mq.keystroke('Right Right').typedText('4');
  6072. assert.equal(mq.latex(), '1\\sqrt[2n]{3x}4');
  6073. });
  6074. });
  6075. });
  6076. suite('sumStartsWithNEquals', function() {
  6077. test('sum defaults to empty limits', function() {
  6078. var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
  6079. assert.equal(mq.latex(), '');
  6080. mq.cmd('\\sum');
  6081. assert.equal(mq.latex(), '\\sum_{ }^{ }');
  6082. mq.cmd('n');
  6083. assert.equal(mq.latex(), '\\sum_n^{ }', 'cursor in lower limit');
  6084. $(mq.el()).remove();
  6085. });
  6086. test('sum starts with `n=`', function() {
  6087. var mq = MQ.MathField($('<span>').appendTo('#mock')[0], {
  6088. sumStartsWithNEquals: true
  6089. });
  6090. assert.equal(mq.latex(), '');
  6091. mq.cmd('\\sum');
  6092. assert.equal(mq.latex(), '\\sum_{n=}^{ }');
  6093. mq.cmd('0');
  6094. assert.equal(mq.latex(), '\\sum_{n=0}^{ }', 'cursor after the `n=`');
  6095. $(mq.el()).remove();
  6096. });
  6097. });
  6098. suite('substituteTextarea', function() {
  6099. test('doesn\'t blow up on selection', function() {
  6100. var mq = MQ.MathField($('<span>').appendTo('#mock')[0], {
  6101. substituteTextarea: function() {
  6102. return $('<span tabindex=0 style="display:inline-block;width:1px;height:1px" />')[0];
  6103. }
  6104. });
  6105. assert.equal(mq.latex(), '');
  6106. mq.write('asdf');
  6107. mq.select();
  6108. $(mq.el()).remove();
  6109. });
  6110. });
  6111. suite('dropEmbedded', function() {
  6112. test('inserts into empty', function() {
  6113. var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
  6114. mq.dropEmbedded(0, 0, {
  6115. htmlString: '<span class="embedded-html"></span>',
  6116. text: function () { return "embedded text" },
  6117. latex: function () { return "embedded latex" }
  6118. });
  6119. assert.ok(jQuery('.embedded-html').length);
  6120. assert.equal(mq.text(), "embedded text");
  6121. assert.equal(mq.latex(), "embedded latex");
  6122. $(mq.el()).remove();
  6123. });
  6124. test('inserts at coordinates', function() {
  6125. // Insert filler so that the page is taller than the window so this test is deterministic
  6126. // Test that we use clientY instead of pageY
  6127. var windowHeight = $(window).height();
  6128. var filler = $('<div>').height(windowHeight);
  6129. filler.insertBefore('#mock');
  6130. var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
  6131. mq.typedText("mmmm/mmmm");
  6132. var pos = $(mq.el()).offset();
  6133. var mqx = pos.left;
  6134. var mqy = pos.top;
  6135. mq.el().scrollIntoView();
  6136. mq.dropEmbedded(mqx + 30, mqy + 40, {
  6137. htmlString: '<span class="embedded-html"></span>',
  6138. text: function () { return "embedded text" },
  6139. latex: function () { return "embedded latex" }
  6140. });
  6141. assert.ok(jQuery('.embedded-html').length);
  6142. assert.equal(mq.text(), "(m*m*m*m)/(m*m*embedded text*m*m)");
  6143. assert.equal(mq.latex(), "\\frac{mmmm}{mmembedded latexmm}");
  6144. filler.remove();
  6145. $(mq.el()).remove();
  6146. });
  6147. });
  6148. test('.registerEmbed()', function() {
  6149. var calls = 0, data;
  6150. MQ.registerEmbed('thing', function(data_) {
  6151. calls += 1;
  6152. data = data_;
  6153. return {
  6154. htmlString: '<span class="embedded-html"></span>',
  6155. text: function () { return "embedded text" },
  6156. latex: function () { return "embedded latex" }
  6157. };
  6158. });
  6159. var mq = MQ.MathField($('<span>\\sqrt{\\embed{thing}}</span>').appendTo('#mock')[0]);
  6160. assert.equal(calls, 1);
  6161. assert.equal(data, undefined);
  6162. assert.ok(jQuery('.embedded-html').length);
  6163. assert.equal(mq.text(), "sqrt(embedded text)");
  6164. assert.equal(mq.latex(), "\\sqrt{embedded latex}");
  6165. mq.latex('\\sqrt{\\embed{thing}[data]}');
  6166. assert.equal(calls, 2);
  6167. assert.equal(data, 'data');
  6168. assert.ok(jQuery('.embedded-html').length);
  6169. assert.equal(mq.text(), "sqrt(embedded text)");
  6170. assert.equal(mq.latex(), "\\sqrt{embedded latex}");
  6171. $(mq.el()).remove();
  6172. });
  6173. });
  6174. suite('saneKeyboardEvents', function() {
  6175. var el;
  6176. var Event = jQuery.Event
  6177. function supportsSelectionAPI() {
  6178. return 'selectionStart' in el[0];
  6179. }
  6180. setup(function() {
  6181. el = $('<textarea>').appendTo('#mock');
  6182. });
  6183. teardown(function() {
  6184. el.remove();
  6185. });
  6186. test('normal keys', function(done) {
  6187. var counter = 0;
  6188. saneKeyboardEvents(el, {
  6189. keystroke: noop,
  6190. typedText: function(text, keydown, keypress) {
  6191. counter += 1;
  6192. assert.ok(counter <= 1, 'callback is only called once');
  6193. assert.equal(text, 'a', 'text comes back as a');
  6194. assert.equal(el.val(), '', 'the textarea remains empty');
  6195. done();
  6196. }
  6197. });
  6198. el.trigger(Event('keydown', { which: 97 }));
  6199. el.trigger(Event('keypress', { which: 97 }));
  6200. el.val('a');
  6201. });
  6202. test('one keydown only', function(done) {
  6203. var counter = 0;
  6204. saneKeyboardEvents(el, {
  6205. keystroke: function(key, evt) {
  6206. counter += 1;
  6207. assert.ok(counter <= 1, 'callback is called only once');
  6208. assert.equal(key, 'Backspace', 'key is correctly set');
  6209. done();
  6210. }
  6211. });
  6212. el.trigger(Event('keydown', { which: 8 }));
  6213. });
  6214. test('a series of keydowns only', function(done) {
  6215. var counter = 0;
  6216. saneKeyboardEvents(el, {
  6217. keystroke: function(key, keydown) {
  6218. counter += 1;
  6219. assert.ok(counter <= 3, 'callback is called at most 3 times');
  6220. assert.ok(keydown);
  6221. assert.equal(key, 'Left');
  6222. if (counter === 3) done();
  6223. }
  6224. });
  6225. el.trigger(Event('keydown', { which: 37 }));
  6226. el.trigger(Event('keydown', { which: 37 }));
  6227. el.trigger(Event('keydown', { which: 37 }));
  6228. });
  6229. test('one keydown and a series of keypresses', function(done) {
  6230. var counter = 0;
  6231. saneKeyboardEvents(el, {
  6232. keystroke: function(key, keydown) {
  6233. counter += 1;
  6234. assert.ok(counter <= 3, 'callback is called at most 3 times');
  6235. assert.ok(keydown);
  6236. assert.equal(key, 'Backspace');
  6237. if (counter === 3) done();
  6238. }
  6239. });
  6240. el.trigger(Event('keydown', { which: 8 }));
  6241. el.trigger(Event('keypress', { which: 8 }));
  6242. el.trigger(Event('keypress', { which: 8 }));
  6243. el.trigger(Event('keypress', { which: 8 }));
  6244. });
  6245. suite('select', function() {
  6246. test('select populates the textarea but doesn\'t call .typedText()', function() {
  6247. var shim = saneKeyboardEvents(el, { keystroke: noop });
  6248. shim.select('foobar');
  6249. assert.equal(el.val(), 'foobar');
  6250. el.trigger('keydown');
  6251. assert.equal(el.val(), 'foobar', 'value remains after keydown');
  6252. if (supportsSelectionAPI()) {
  6253. el.trigger('keypress');
  6254. assert.equal(el.val(), 'foobar', 'value remains after keypress');
  6255. el.trigger('input');
  6256. assert.equal(el.val(), 'foobar', 'value remains after flush after keypress');
  6257. }
  6258. });
  6259. test('select populates the textarea but doesn\'t call text' +
  6260. ' on keydown, even when the selection is not properly' +
  6261. ' detectable', function() {
  6262. var shim = saneKeyboardEvents(el, { keystroke: noop });
  6263. shim.select('foobar');
  6264. // monkey-patch the dom-level selection so that hasSelection()
  6265. // returns false, as in IE < 9.
  6266. el[0].selectionStart = el[0].selectionEnd = 0;
  6267. el.trigger('keydown');
  6268. assert.equal(el.val(), 'foobar', 'value remains after keydown');
  6269. });
  6270. test('blurring', function() {
  6271. var shim = saneKeyboardEvents(el, { keystroke: noop });
  6272. shim.select('foobar');
  6273. el.trigger('blur');
  6274. el.focus();
  6275. // IE < 9 doesn't support selection{Start,End}
  6276. if (supportsSelectionAPI()) {
  6277. assert.equal(el[0].selectionStart, 0, 'it\'s selected from the start');
  6278. assert.equal(el[0].selectionEnd, 6, 'it\'s selected to the end');
  6279. }
  6280. assert.equal(el.val(), 'foobar', 'it still has content');
  6281. });
  6282. test('blur then empty selection', function() {
  6283. var shim = saneKeyboardEvents(el, { keystroke: noop });
  6284. shim.select('foobar');
  6285. el.blur();
  6286. shim.select('');
  6287. assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
  6288. });
  6289. if (!document.hasFocus()) {
  6290. test('blur in keystroke handler: DOCUMENT NEEDS FOCUS, SEE CONSOLE ');
  6291. console.warn(
  6292. 'The test "blur in keystroke handler" needs the document to have ' +
  6293. 'focus. Only when the document has focus does .select() on an ' +
  6294. 'element also focus it, which is part of the problematic behavior ' +
  6295. 'we are testing robustness against. (Specifically, erroneously ' +
  6296. 'calling .select() in a timeout after the textarea has blurred, ' +
  6297. '"stealing back" focus.)\n' +
  6298. 'Normally, the page being open and focused is enough to have focus, ' +
  6299. 'but with the Developer Tools open, it depends on whether you last ' +
  6300. 'clicked on something in the Developer Tools or on the page itself. ' +
  6301. 'Click the page, or close the Developer Tools, and Refresh.'
  6302. );
  6303. }
  6304. else {
  6305. test('blur in keystroke handler', function(done) {
  6306. var shim = saneKeyboardEvents(el, {
  6307. keystroke: function(key) {
  6308. assert.equal(key, 'Left');
  6309. el[0].blur();
  6310. }
  6311. });
  6312. shim.select('foobar');
  6313. assert.ok(document.activeElement === el[0], 'textarea focused');
  6314. el.trigger(Event('keydown', { which: 37 }));
  6315. assert.ok(document.activeElement !== el[0], 'textarea blurred');
  6316. setTimeout(function() {
  6317. assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
  6318. done();
  6319. });
  6320. });
  6321. }
  6322. suite('selected text after keypress or paste doesn\'t get mistaken' +
  6323. ' for inputted text', function() {
  6324. test('select() immediately after paste', function() {
  6325. var pastedText;
  6326. var onPaste = function(text) { pastedText = text; };
  6327. var shim = saneKeyboardEvents(el, {
  6328. paste: function(text) { onPaste(text); }
  6329. });
  6330. el.trigger('paste').val('$x^2+1$');
  6331. shim.select('$\\frac{x^2+1}{2}$');
  6332. assert.equal(pastedText, '$x^2+1$');
  6333. assert.equal(el.val(), '$\\frac{x^2+1}{2}$');
  6334. onPaste = null;
  6335. shim.select('$2$');
  6336. assert.equal(el.val(), '$2$');
  6337. });
  6338. test('select() after paste/input', function() {
  6339. var pastedText;
  6340. var onPaste = function(text) { pastedText = text; };
  6341. var shim = saneKeyboardEvents(el, {
  6342. paste: function(text) { onPaste(text); }
  6343. });
  6344. el.trigger('paste').val('$x^2+1$');
  6345. el.trigger('input');
  6346. assert.equal(pastedText, '$x^2+1$');
  6347. assert.equal(el.val(), '');
  6348. onPaste = null;
  6349. shim.select('$\\frac{x^2+1}{2}$');
  6350. assert.equal(el.val(), '$\\frac{x^2+1}{2}$');
  6351. shim.select('$2$');
  6352. assert.equal(el.val(), '$2$');
  6353. });
  6354. test('select() immediately after keydown/keypress', function() {
  6355. var typedText;
  6356. var onText = function(text) { typedText = text; };
  6357. var shim = saneKeyboardEvents(el, {
  6358. keystroke: noop,
  6359. typedText: function(text) { onText(text); }
  6360. });
  6361. el.trigger(Event('keydown', { which: 97 }));
  6362. el.trigger(Event('keypress', { which: 97 }));
  6363. el.val('a');
  6364. shim.select('$\\frac{a}{2}$');
  6365. assert.equal(typedText, 'a');
  6366. assert.equal(el.val(), '$\\frac{a}{2}$');
  6367. onText = null;
  6368. shim.select('$2$');
  6369. assert.equal(el.val(), '$2$');
  6370. });
  6371. test('select() after keydown/keypress/input', function() {
  6372. var typedText;
  6373. var onText = function(text) { typedText = text; };
  6374. var shim = saneKeyboardEvents(el, {
  6375. keystroke: noop,
  6376. typedText: function(text) { onText(text); }
  6377. });
  6378. el.trigger(Event('keydown', { which: 97 }));
  6379. el.trigger(Event('keypress', { which: 97 }));
  6380. el.val('a');
  6381. el.trigger('input');
  6382. assert.equal(typedText, 'a');
  6383. onText = null;
  6384. shim.select('$\\frac{a}{2}$');
  6385. assert.equal(el.val(), '$\\frac{a}{2}$');
  6386. shim.select('$2$');
  6387. assert.equal(el.val(), '$2$');
  6388. });
  6389. suite('unrecognized keys that move cursor and clear selection', function() {
  6390. test('without keypress', function() {
  6391. var shim = saneKeyboardEvents(el, { keystroke: noop });
  6392. shim.select('a');
  6393. assert.equal(el.val(), 'a');
  6394. if (!supportsSelectionAPI()) return;
  6395. el.trigger(Event('keydown', { which: 37, altKey: true }));
  6396. el[0].selectionEnd = 0;
  6397. el.trigger(Event('keyup', { which: 37, altKey: true }));
  6398. assert.ok(el[0].selectionStart !== el[0].selectionEnd);
  6399. el.blur();
  6400. shim.select('');
  6401. assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
  6402. });
  6403. test('with keypress, many characters selected', function() {
  6404. var shim = saneKeyboardEvents(el, { keystroke: noop });
  6405. shim.select('many characters');
  6406. assert.equal(el.val(), 'many characters');
  6407. if (!supportsSelectionAPI()) return;
  6408. el.trigger(Event('keydown', { which: 37, altKey: true }));
  6409. el.trigger(Event('keypress', { which: 37, altKey: true }));
  6410. el[0].selectionEnd = 0;
  6411. el.trigger('keyup');
  6412. assert.ok(el[0].selectionStart !== el[0].selectionEnd);
  6413. el.blur();
  6414. shim.select('');
  6415. assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
  6416. });
  6417. test('with keypress, only 1 character selected', function() {
  6418. var count = 0;
  6419. var shim = saneKeyboardEvents(el, {
  6420. keystroke: noop,
  6421. typedText: function(ch) {
  6422. assert.equal(ch, 'a');
  6423. assert.equal(el.val(), '');
  6424. count += 1;
  6425. }
  6426. });
  6427. shim.select('a');
  6428. assert.equal(el.val(), 'a');
  6429. if (!supportsSelectionAPI()) return;
  6430. el.trigger(Event('keydown', { which: 37, altKey: true }));
  6431. el.trigger(Event('keypress', { which: 37, altKey: true }));
  6432. el[0].selectionEnd = 0;
  6433. el.trigger('keyup');
  6434. assert.equal(count, 1);
  6435. el.blur();
  6436. shim.select('');
  6437. assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
  6438. });
  6439. });
  6440. });
  6441. });
  6442. suite('paste', function() {
  6443. test('paste event only', function(done) {
  6444. saneKeyboardEvents(el, {
  6445. paste: function(text) {
  6446. assert.equal(text, '$x^2+1$');
  6447. done();
  6448. }
  6449. });
  6450. el.trigger('paste');
  6451. el.val('$x^2+1$');
  6452. });
  6453. test('paste after keydown/keypress', function(done) {
  6454. saneKeyboardEvents(el, {
  6455. keystroke: noop,
  6456. paste: function(text) {
  6457. assert.equal(text, 'foobar');
  6458. done();
  6459. }
  6460. });
  6461. // Ctrl-V in Firefox or Opera, according to unixpapa.com/js/key.html
  6462. // without an `input` event
  6463. el.trigger('keydown', { which: 86, ctrlKey: true });
  6464. el.trigger('keypress', { which: 118, ctrlKey: true });
  6465. el.trigger('paste');
  6466. el.val('foobar');
  6467. });
  6468. test('paste after keydown/keypress/input', function(done) {
  6469. saneKeyboardEvents(el, {
  6470. keystroke: noop,
  6471. paste: function(text) {
  6472. assert.equal(text, 'foobar');
  6473. done();
  6474. }
  6475. });
  6476. // Ctrl-V in Firefox or Opera, according to unixpapa.com/js/key.html
  6477. // with an `input` event
  6478. el.trigger('keydown', { which: 86, ctrlKey: true });
  6479. el.trigger('keypress', { which: 118, ctrlKey: true });
  6480. el.trigger('paste');
  6481. el.val('foobar');
  6482. el.trigger('input');
  6483. });
  6484. test('keypress timeout happening before paste timeout', function(done) {
  6485. saneKeyboardEvents(el, {
  6486. keystroke: noop,
  6487. paste: function(text) {
  6488. assert.equal(text, 'foobar');
  6489. done();
  6490. }
  6491. });
  6492. el.trigger('keydown', { which: 86, ctrlKey: true });
  6493. el.trigger('keypress', { which: 118, ctrlKey: true });
  6494. el.trigger('paste');
  6495. el.val('foobar');
  6496. // this synthesizes the keypress timeout calling handleText()
  6497. // before the paste timeout happens.
  6498. el.trigger('input');
  6499. });
  6500. });
  6501. });
  6502. suite('Cursor::select()', function() {
  6503. var cursor = Cursor();
  6504. cursor.selectionChanged = noop;
  6505. function assertSelection(A, B, leftEnd, rightEnd) {
  6506. var lca = leftEnd.parent, frag = Fragment(leftEnd, rightEnd || leftEnd);
  6507. (function eitherOrder(A, B) {
  6508. var count = 0;
  6509. lca.selectChildren = function(leftEnd, rightEnd) {
  6510. count += 1;
  6511. assert.equal(frag.ends[L], leftEnd);
  6512. assert.equal(frag.ends[R], rightEnd);
  6513. return Node.p.selectChildren.apply(this, arguments);
  6514. };
  6515. Point.p.init.call(cursor, A.parent, A[L], A[R]);
  6516. cursor.startSelection();
  6517. Point.p.init.call(cursor, B.parent, B[L], B[R]);
  6518. assert.equal(cursor.select(), true);
  6519. assert.equal(count, 1);
  6520. return eitherOrder;
  6521. }(A, B)(B, A));
  6522. }
  6523. var parent = Node();
  6524. var child1 = Node().adopt(parent, parent.ends[R], 0);
  6525. var child2 = Node().adopt(parent, parent.ends[R], 0);
  6526. var child3 = Node().adopt(parent, parent.ends[R], 0);
  6527. var A = Point(parent, 0, child1);
  6528. var B = Point(parent, child1, child2);
  6529. var C = Point(parent, child2, child3);
  6530. var D = Point(parent, child3, 0);
  6531. var pt1 = Point(child1, 0, 0);
  6532. var pt2 = Point(child2, 0, 0);
  6533. var pt3 = Point(child3, 0, 0);
  6534. test('same parent, one Node', function() {
  6535. assertSelection(A, B, child1);
  6536. assertSelection(B, C, child2);
  6537. assertSelection(C, D, child3);
  6538. });
  6539. test('same Parent, many Nodes', function() {
  6540. assertSelection(A, C, child1, child2);
  6541. assertSelection(A, D, child1, child3);
  6542. assertSelection(B, D, child2, child3);
  6543. });
  6544. test('Point next to parent of other Point', function() {
  6545. assertSelection(A, pt1, child1);
  6546. assertSelection(B, pt1, child1);
  6547. assertSelection(B, pt2, child2);
  6548. assertSelection(C, pt2, child2);
  6549. assertSelection(C, pt3, child3);
  6550. assertSelection(D, pt3, child3);
  6551. });
  6552. test('Points\' parents are siblings', function() {
  6553. assertSelection(pt1, pt2, child1, child2);
  6554. assertSelection(pt2, pt3, child2, child3);
  6555. assertSelection(pt1, pt3, child1, child3);
  6556. });
  6557. test('Point is sibling of parent of other Point', function() {
  6558. assertSelection(A, pt2, child1, child2);
  6559. assertSelection(A, pt3, child1, child3);
  6560. assertSelection(B, pt3, child2, child3);
  6561. assertSelection(pt1, D, child1, child3);
  6562. assertSelection(pt1, C, child1, child2);
  6563. });
  6564. test('same Point', function() {
  6565. Point.p.init.call(cursor, A.parent, A[L], A[R]);
  6566. cursor.startSelection();
  6567. assert.equal(cursor.select(), false);
  6568. });
  6569. test('different trees', function() {
  6570. var anotherTree = Node();
  6571. Point.p.init.call(cursor, A.parent, A[L], A[R]);
  6572. cursor.startSelection();
  6573. Point.p.init.call(cursor, anotherTree, 0, 0);
  6574. assert.throws(function() { cursor.select(); });
  6575. Point.p.init.call(cursor, anotherTree, 0, 0);
  6576. cursor.startSelection();
  6577. Point.p.init.call(cursor, A.parent, A[L], A[R]);
  6578. assert.throws(function() { cursor.select(); });
  6579. });
  6580. });
  6581. suite('text', function() {
  6582. function fromLatex(latex) {
  6583. var block = latexMathParser.parse(latex);
  6584. block.jQize();
  6585. return block;
  6586. }
  6587. function assertSplit(jQ, prev, next) {
  6588. var dom = jQ[0];
  6589. if (prev) {
  6590. assert.ok(dom.previousSibling instanceof Text);
  6591. assert.equal(prev, dom.previousSibling.data);
  6592. }
  6593. else {
  6594. assert.ok(!dom.previousSibling);
  6595. }
  6596. if (next) {
  6597. assert.ok(dom.nextSibling instanceof Text);
  6598. assert.equal(next, dom.nextSibling.data);
  6599. }
  6600. else {
  6601. assert.ok(!dom.nextSibling);
  6602. }
  6603. }
  6604. test('changes the text nodes as the cursor moves around', function() {
  6605. var block = fromLatex('\\text{abc}');
  6606. var ctrlr = Controller(block, 0, 0);
  6607. var cursor = ctrlr.cursor.insAtRightEnd(block);
  6608. ctrlr.moveLeft();
  6609. assertSplit(cursor.jQ, 'abc', null);
  6610. ctrlr.moveLeft();
  6611. assertSplit(cursor.jQ, 'ab', 'c');
  6612. ctrlr.moveLeft();
  6613. assertSplit(cursor.jQ, 'a', 'bc');
  6614. ctrlr.moveLeft();
  6615. assertSplit(cursor.jQ, null, 'abc');
  6616. ctrlr.moveRight();
  6617. assertSplit(cursor.jQ, 'a', 'bc');
  6618. ctrlr.moveRight();
  6619. assertSplit(cursor.jQ, 'ab', 'c');
  6620. ctrlr.moveRight();
  6621. assertSplit(cursor.jQ, 'abc', null);
  6622. });
  6623. test('does not change latex as the cursor moves around', function() {
  6624. var block = fromLatex('\\text{x}');
  6625. var ctrlr = Controller(block, 0, 0);
  6626. var cursor = ctrlr.cursor.insAtRightEnd(block);
  6627. ctrlr.moveLeft();
  6628. ctrlr.moveLeft();
  6629. ctrlr.moveLeft();
  6630. assert.equal(block.latex(), '\\text{x}');
  6631. });
  6632. });
  6633. suite('tree', function() {
  6634. suite('adopt', function() {
  6635. function assertTwoChildren(parent, one, two) {
  6636. assert.equal(one.parent, parent, 'one.parent is set');
  6637. assert.equal(two.parent, parent, 'two.parent is set');
  6638. assert.ok(!one[L], 'one has nothing leftward');
  6639. assert.equal(one[R], two, 'one[R] is two');
  6640. assert.equal(two[L], one, 'two[L] is one');
  6641. assert.ok(!two[R], 'two has nothing rightward');
  6642. assert.equal(parent.ends[L], one, 'parent.ends[L] is one');
  6643. assert.equal(parent.ends[R], two, 'parent.ends[R] is two');
  6644. }
  6645. test('the empty case', function() {
  6646. var parent = Node();
  6647. var child = Node();
  6648. child.adopt(parent, 0, 0);
  6649. assert.equal(child.parent, parent, 'child.parent is set');
  6650. assert.ok(!child[R], 'child has nothing rightward');
  6651. assert.ok(!child[L], 'child has nothing leftward');
  6652. assert.equal(parent.ends[L], child, 'child is parent.ends[L]');
  6653. assert.equal(parent.ends[R], child, 'child is parent.ends[R]');
  6654. });
  6655. test('with two children from the left', function() {
  6656. var parent = Node();
  6657. var one = Node();
  6658. var two = Node();
  6659. one.adopt(parent, 0, 0);
  6660. two.adopt(parent, one, 0);
  6661. assertTwoChildren(parent, one, two);
  6662. });
  6663. test('with two children from the right', function() {
  6664. var parent = Node();
  6665. var one = Node();
  6666. var two = Node();
  6667. two.adopt(parent, 0, 0);
  6668. one.adopt(parent, 0, two);
  6669. assertTwoChildren(parent, one, two);
  6670. });
  6671. test('adding one in the middle', function() {
  6672. var parent = Node();
  6673. var leftward = Node();
  6674. var rightward = Node();
  6675. var middle = Node();
  6676. leftward.adopt(parent, 0, 0);
  6677. rightward.adopt(parent, leftward, 0);
  6678. middle.adopt(parent, leftward, rightward);
  6679. assert.equal(middle.parent, parent, 'middle.parent is set');
  6680. assert.equal(middle[L], leftward, 'middle[L] is set');
  6681. assert.equal(middle[R], rightward, 'middle[R] is set');
  6682. assert.equal(leftward[R], middle, 'leftward[R] is middle');
  6683. assert.equal(rightward[L], middle, 'rightward[L] is middle');
  6684. assert.equal(parent.ends[L], leftward, 'parent.ends[L] is leftward');
  6685. assert.equal(parent.ends[R], rightward, 'parent.ends[R] is rightward');
  6686. });
  6687. });
  6688. suite('disown', function() {
  6689. function assertSingleChild(parent, child) {
  6690. assert.equal(parent.ends[L], child, 'parent.ends[L] is child');
  6691. assert.equal(parent.ends[R], child, 'parent.ends[R] is child');
  6692. assert.ok(!child[L], 'child has nothing leftward');
  6693. assert.ok(!child[R], 'child has nothing rightward');
  6694. }
  6695. test('the empty case', function() {
  6696. var parent = Node();
  6697. var child = Node();
  6698. child.adopt(parent, 0, 0);
  6699. child.disown();
  6700. assert.ok(!parent.ends[L], 'parent has no left end child');
  6701. assert.ok(!parent.ends[R], 'parent has no right end child');
  6702. });
  6703. test('disowning the right end child', function() {
  6704. var parent = Node();
  6705. var one = Node();
  6706. var two = Node();
  6707. one.adopt(parent, 0, 0);
  6708. two.adopt(parent, one, 0);
  6709. two.disown();
  6710. assertSingleChild(parent, one);
  6711. assert.equal(two.parent, parent, 'two retains its parent');
  6712. assert.equal(two[L], one, 'two retains its [L]');
  6713. assert.throws(function() { two.disown(); },
  6714. 'disown fails on a malformed tree');
  6715. });
  6716. test('disowning the left end child', function() {
  6717. var parent = Node();
  6718. var one = Node();
  6719. var two = Node();
  6720. one.adopt(parent, 0, 0);
  6721. two.adopt(parent, one, 0);
  6722. one.disown();
  6723. assertSingleChild(parent, two);
  6724. assert.equal(one.parent, parent, 'one retains its parent');
  6725. assert.equal(one[R], two, 'one retains its [R]');
  6726. assert.throws(function() { one.disown(); },
  6727. 'disown fails on a malformed tree');
  6728. });
  6729. test('disowning the middle', function() {
  6730. var parent = Node();
  6731. var leftward = Node();
  6732. var rightward = Node();
  6733. var middle = Node();
  6734. leftward.adopt(parent, 0, 0);
  6735. rightward.adopt(parent, leftward, 0);
  6736. middle.adopt(parent, leftward, rightward);
  6737. middle.disown();
  6738. assert.equal(leftward[R], rightward, 'leftward[R] is rightward');
  6739. assert.equal(rightward[L], leftward, 'rightward[L] is leftward');
  6740. assert.equal(parent.ends[L], leftward, 'parent.ends[L] is leftward');
  6741. assert.equal(parent.ends[R], rightward, 'parent.ends[R] is rightward');
  6742. assert.equal(middle.parent, parent, 'middle retains its parent');
  6743. assert.equal(middle[R], rightward, 'middle retains its [R]');
  6744. assert.equal(middle[L], leftward, 'middle retains its [L]');
  6745. assert.throws(function() { middle.disown(); },
  6746. 'disown fails on a malformed tree');
  6747. });
  6748. });
  6749. suite('fragments', function() {
  6750. test('an empty fragment', function() {
  6751. var empty = Fragment();
  6752. var count = 0;
  6753. empty.each(function() { count += 1 });
  6754. assert.equal(count, 0, 'each is a noop on an empty fragment');
  6755. });
  6756. test('half-empty fragments are disallowed', function() {
  6757. assert.throws(function() {
  6758. Fragment(Node(), 0)
  6759. }, 'half-empty on the right');
  6760. assert.throws(function() {
  6761. Fragment(0, Node());
  6762. }, 'half-empty on the left');
  6763. });
  6764. test('directionalized constructor call', function() {
  6765. var ChNode = P(Node, { init: function(ch) { this.ch = ch; } });
  6766. var parent = Node();
  6767. var a = ChNode('a').adopt(parent, parent.ends[R], 0);
  6768. var b = ChNode('b').adopt(parent, parent.ends[R], 0);
  6769. var c = ChNode('c').adopt(parent, parent.ends[R], 0);
  6770. var d = ChNode('d').adopt(parent, parent.ends[R], 0);
  6771. var e = ChNode('e').adopt(parent, parent.ends[R], 0);
  6772. function cat(str, node) { return str + node.ch; }
  6773. assert.equal('bcd', Fragment(b, d).fold('', cat));
  6774. assert.equal('bcd', Fragment(b, d, L).fold('', cat));
  6775. assert.equal('bcd', Fragment(d, b, R).fold('', cat));
  6776. assert.throws(function() { Fragment(d, b, L); });
  6777. assert.throws(function() { Fragment(b, d, R); });
  6778. });
  6779. test('disown is idempotent', function() {
  6780. var parent = Node();
  6781. var one = Node().adopt(parent, 0, 0);
  6782. var two = Node().adopt(parent, one, 0);
  6783. var frag = Fragment(one, two);
  6784. frag.disown();
  6785. frag.disown();
  6786. });
  6787. });
  6788. });
  6789. suite('typing with auto-replaces', function() {
  6790. var mq;
  6791. setup(function() {
  6792. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  6793. });
  6794. teardown(function() {
  6795. $(mq.el()).remove();
  6796. });
  6797. function prayWellFormedPoint(pt) { prayWellFormed(pt.parent, pt[L], pt[R]); }
  6798. function assertLatex(latex) {
  6799. prayWellFormedPoint(mq.__controller.cursor);
  6800. assert.equal(mq.latex(), latex);
  6801. }
  6802. suite('LiveFraction', function() {
  6803. test('full MathQuill', function() {
  6804. mq.typedText('1/2').keystroke('Tab').typedText('+sinx/');
  6805. assertLatex('\\frac{1}{2}+\\frac{\\sin x}{ }');
  6806. mq.latex('').typedText('1+/2');
  6807. assertLatex('1+\\frac{2}{ }');
  6808. mq.latex('').typedText('1 2/3');
  6809. assertLatex('1\\ \\frac{2}{3}');
  6810. });
  6811. test('mathquill-basic', function() {
  6812. var mq_basic = MQBasic.MathField($('<span></span>').appendTo('#mock')[0]);
  6813. mq_basic.typedText('1/2');
  6814. assert.equal(mq_basic.latex(), '\\frac{1}{2}');
  6815. $(mq_basic.el()).remove();
  6816. });
  6817. });
  6818. suite('LatexCommandInput', function() {
  6819. test('basic', function() {
  6820. mq.typedText('\\sqrt-x');
  6821. assertLatex('\\sqrt{-x}');
  6822. });
  6823. test('they\'re passed their name', function() {
  6824. mq.cmd('\\alpha');
  6825. assert.equal(mq.latex(), '\\alpha');
  6826. });
  6827. test('replaces selection', function() {
  6828. mq.typedText('49').select().typedText('\\sqrt').keystroke('Enter');
  6829. assertLatex('\\sqrt{49}');
  6830. });
  6831. test('auto-operator names', function() {
  6832. mq.typedText('\\sin^2');
  6833. assertLatex('\\sin^2');
  6834. });
  6835. test('nonexistent LaTeX command', function() {
  6836. mq.typedText('\\asdf+');
  6837. assertLatex('\\text{asdf}+');
  6838. });
  6839. });
  6840. suite('auto-expanding parens', function() {
  6841. suite('simple', function() {
  6842. test('empty parens ()', function() {
  6843. mq.typedText('(');
  6844. assertLatex('\\left(\\right)');
  6845. mq.typedText(')');
  6846. assertLatex('\\left(\\right)');
  6847. });
  6848. test('straight typing 1+(2+3)+4', function() {
  6849. mq.typedText('1+(2+3)+4');
  6850. assertLatex('1+\\left(2+3\\right)+4');
  6851. });
  6852. test('basic command \\sin(', function () {
  6853. mq.typedText('\\sin(');
  6854. assertLatex('\\sin\\left(\\right)');
  6855. });
  6856. test('wrapping things in parens 1+(2+3)+4', function() {
  6857. mq.typedText('1+2+3+4');
  6858. assertLatex('1+2+3+4');
  6859. mq.keystroke('Left Left').typedText(')');
  6860. assertLatex('\\left(1+2+3\\right)+4');
  6861. mq.keystroke('Left Left Left Left').typedText('(');
  6862. assertLatex('1+\\left(2+3\\right)+4');
  6863. });
  6864. test('nested parens 1+(2+(3+4)+5)+6', function() {
  6865. mq.typedText('1+(2+(3+4)+5)+6');
  6866. assertLatex('1+\\left(2+\\left(3+4\\right)+5\\right)+6');
  6867. });
  6868. });
  6869. suite('mismatched brackets', function() {
  6870. test('empty mismatched brackets (] and [}', function() {
  6871. mq.typedText('(');
  6872. assertLatex('\\left(\\right)');
  6873. mq.typedText(']');
  6874. assertLatex('\\left(\\right]');
  6875. mq.typedText('[');
  6876. assertLatex('\\left(\\right]\\left[\\right]');
  6877. mq.typedText('}');
  6878. assertLatex('\\left(\\right]\\left[\\right\\}');
  6879. });
  6880. test('typing mismatched brackets 1+(2+3]+4', function() {
  6881. mq.typedText('1+');
  6882. assertLatex('1+');
  6883. mq.typedText('(');
  6884. assertLatex('1+\\left(\\right)');
  6885. mq.typedText('2+3');
  6886. assertLatex('1+\\left(2+3\\right)');
  6887. mq.typedText(']+4');
  6888. assertLatex('1+\\left(2+3\\right]+4');
  6889. });
  6890. test('wrapping things in mismatched brackets 1+(2+3]+4', function() {
  6891. mq.typedText('1+2+3+4');
  6892. assertLatex('1+2+3+4');
  6893. mq.keystroke('Left Left').typedText(']');
  6894. assertLatex('\\left[1+2+3\\right]+4');
  6895. mq.keystroke('Left Left Left Left').typedText('(');
  6896. assertLatex('1+\\left(2+3\\right]+4');
  6897. });
  6898. test('nested mismatched brackets 1+(2+[3+4)+5]+6', function() {
  6899. mq.typedText('1+(2+[3+4)+5]+6');
  6900. assertLatex('1+\\left(2+\\left[3+4\\right)+5\\right]+6');
  6901. });
  6902. suite('restrictMismatchedBrackets', function() {
  6903. setup(function() {
  6904. mq.config({ restrictMismatchedBrackets: true });
  6905. });
  6906. test('typing (|x|+1) works', function() {
  6907. mq.typedText('(|x|+1)');
  6908. assertLatex('\\left(\\left|x\\right|+1\\right)');
  6909. });
  6910. test('typing [x} becomes [{x}]', function() {
  6911. mq.typedText('[x}');
  6912. assertLatex('\\left[\\left\\{x\\right\\}\\right]');
  6913. });
  6914. test('normal matching pairs {f(n), [a,b]} work', function() {
  6915. mq.typedText('{f(n), [a,b]}');
  6916. assertLatex('\\left\\{f\\left(n\\right),\\ \\left[a,b\\right]\\right\\}');
  6917. });
  6918. test('[a,b) and (a,b] still work', function() {
  6919. mq.typedText('[a,b) + (a,b]');
  6920. assertLatex('\\left[a,b\\right)\\ +\\ \\left(a,b\\right]');
  6921. });
  6922. });
  6923. });
  6924. suite('pipes', function() {
  6925. test('empty pipes ||', function() {
  6926. mq.typedText('|');
  6927. assertLatex('\\left|\\right|');
  6928. mq.typedText('|');
  6929. assertLatex('\\left|\\right|');
  6930. });
  6931. test('straight typing 1+|2+3|+4', function() {
  6932. mq.typedText('1+|2+3|+4');
  6933. assertLatex('1+\\left|2+3\\right|+4');
  6934. });
  6935. test('wrapping things in pipes 1+|2+3|+4', function() {
  6936. mq.typedText('1+2+3+4');
  6937. assertLatex('1+2+3+4');
  6938. mq.keystroke('Home Right Right').typedText('|');
  6939. assertLatex('1+\\left|2+3+4\\right|');
  6940. mq.keystroke('Right Right Right').typedText('|');
  6941. assertLatex('1+\\left|2+3\\right|+4');
  6942. });
  6943. suite('can type mismatched paren/pipe group from any side', function() {
  6944. suite('straight typing', function() {
  6945. test('|)', function() {
  6946. mq.typedText('|)');
  6947. assertLatex('\\left|\\right)');
  6948. });
  6949. test('(|', function() {
  6950. mq.typedText('(|');
  6951. assertLatex('\\left(\\right|');
  6952. });
  6953. });
  6954. suite('the other direction', function() {
  6955. test('|)', function() {
  6956. mq.typedText(')');
  6957. assertLatex('\\left(\\right)');
  6958. mq.keystroke('Left').typedText('|');
  6959. assertLatex('\\left|\\right)');
  6960. });
  6961. test('(|', function() {
  6962. mq.typedText('||');
  6963. assertLatex('\\left|\\right|');
  6964. mq.keystroke('Left Left Del');
  6965. assertLatex('\\left|\\right|');
  6966. mq.typedText('(');
  6967. assertLatex('\\left(\\right|');
  6968. });
  6969. });
  6970. });
  6971. });
  6972. suite('backspacing', backspacingTests);
  6973. suite('backspacing with restrictMismatchedBrackets', function() {
  6974. setup(function() {
  6975. mq.config({ restrictMismatchedBrackets: true });
  6976. });
  6977. backspacingTests();
  6978. });
  6979. function backspacingTests() {
  6980. test('typing then backspacing a close-paren in the middle of 1+2+3+4', function() {
  6981. mq.typedText('1+2+3+4');
  6982. assertLatex('1+2+3+4');
  6983. mq.keystroke('Left Left').typedText(')');
  6984. assertLatex('\\left(1+2+3\\right)+4');
  6985. mq.keystroke('Backspace');
  6986. assertLatex('1+2+3+4');
  6987. });
  6988. test('backspacing close-paren then open-paren of 1+(2+3)+4', function() {
  6989. mq.typedText('1+(2+3)+4');
  6990. assertLatex('1+\\left(2+3\\right)+4');
  6991. mq.keystroke('Left Left Backspace');
  6992. assertLatex('1+\\left(2+3+4\\right)');
  6993. mq.keystroke('Left Left Left Backspace');
  6994. assertLatex('1+2+3+4');
  6995. });
  6996. test('backspacing open-paren of 1+(2+3)+4', function() {
  6997. mq.typedText('1+(2+3)+4');
  6998. assertLatex('1+\\left(2+3\\right)+4');
  6999. mq.keystroke('Left Left Left Left Left Left Backspace');
  7000. assertLatex('1+2+3+4');
  7001. });
  7002. test('backspacing close-bracket then open-paren of 1+(2+3]+4', function() {
  7003. mq.typedText('1+(2+3]+4');
  7004. assertLatex('1+\\left(2+3\\right]+4');
  7005. mq.keystroke('Left Left Backspace');
  7006. assertLatex('1+\\left(2+3+4\\right)');
  7007. mq.keystroke('Left Left Left Backspace');
  7008. assertLatex('1+2+3+4');
  7009. });
  7010. test('backspacing open-paren of 1+(2+3]+4', function() {
  7011. mq.typedText('1+(2+3]+4');
  7012. assertLatex('1+\\left(2+3\\right]+4');
  7013. mq.keystroke('Left Left Left Left Left Left Backspace');
  7014. assertLatex('1+2+3+4');
  7015. });
  7016. test('backspacing close-bracket then open-paren of 1+(2+3] (nothing after paren group)', function() {
  7017. mq.typedText('1+(2+3]');
  7018. assertLatex('1+\\left(2+3\\right]');
  7019. mq.keystroke('Backspace');
  7020. assertLatex('1+\\left(2+3\\right)');
  7021. mq.keystroke('Left Left Left Backspace');
  7022. assertLatex('1+2+3');
  7023. });
  7024. test('backspacing open-paren of 1+(2+3] (nothing after paren group)', function() {
  7025. mq.typedText('1+(2+3]');
  7026. assertLatex('1+\\left(2+3\\right]');
  7027. mq.keystroke('Left Left Left Left Backspace');
  7028. assertLatex('1+2+3');
  7029. });
  7030. test('backspacing close-bracket then open-paren of (2+3]+4 (nothing before paren group)', function() {
  7031. mq.typedText('(2+3]+4');
  7032. assertLatex('\\left(2+3\\right]+4');
  7033. mq.keystroke('Left Left Backspace');
  7034. assertLatex('\\left(2+3+4\\right)');
  7035. mq.keystroke('Left Left Left Backspace');
  7036. assertLatex('2+3+4');
  7037. });
  7038. test('backspacing open-paren of (2+3]+4 (nothing before paren group)', function() {
  7039. mq.typedText('(2+3]+4');
  7040. assertLatex('\\left(2+3\\right]+4');
  7041. mq.keystroke('Left Left Left Left Left Left Backspace');
  7042. assertLatex('2+3+4');
  7043. });
  7044. function assertParenBlockNonEmpty() {
  7045. var parenBlock = $(mq.el()).find('.mq-paren+span');
  7046. assert.equal(parenBlock.length, 1, 'exactly 1 paren block');
  7047. assert.ok(!parenBlock.hasClass('mq-empty'),
  7048. 'paren block auto-expanded, should no longer be gray');
  7049. }
  7050. test('backspacing close-bracket then open-paren of 1+(]+4 (empty paren group)', function() {
  7051. mq.typedText('1+(]+4');
  7052. assertLatex('1+\\left(\\right]+4');
  7053. mq.keystroke('Left Left Backspace');
  7054. assertLatex('1+\\left(+4\\right)');
  7055. assertParenBlockNonEmpty();
  7056. mq.keystroke('Backspace');
  7057. assertLatex('1++4');
  7058. });
  7059. test('backspacing open-paren of 1+(]+4 (empty paren group)', function() {
  7060. mq.typedText('1+(]+4');
  7061. assertLatex('1+\\left(\\right]+4');
  7062. mq.keystroke('Left Left Left Backspace');
  7063. assertLatex('1++4');
  7064. });
  7065. test('backspacing close-bracket then open-paren of 1+(] (empty paren group, nothing after)', function() {
  7066. mq.typedText('1+(]');
  7067. assertLatex('1+\\left(\\right]');
  7068. mq.keystroke('Backspace');
  7069. assertLatex('1+\\left(\\right)');
  7070. mq.keystroke('Backspace');
  7071. assertLatex('1+');
  7072. });
  7073. test('backspacing open-paren of 1+(] (empty paren group, nothing after)', function() {
  7074. mq.typedText('1+(]');
  7075. assertLatex('1+\\left(\\right]');
  7076. mq.keystroke('Left Backspace');
  7077. assertLatex('1+');
  7078. });
  7079. test('backspacing close-bracket then open-paren of (]+4 (empty paren group, nothing before)', function() {
  7080. mq.typedText('(]+4');
  7081. assertLatex('\\left(\\right]+4');
  7082. mq.keystroke('Left Left Backspace');
  7083. assertLatex('\\left(+4\\right)');
  7084. assertParenBlockNonEmpty();
  7085. mq.keystroke('Backspace');
  7086. assertLatex('+4');
  7087. });
  7088. test('backspacing open-paren of (]+4 (empty paren group, nothing before)', function() {
  7089. mq.typedText('(]+4');
  7090. assertLatex('\\left(\\right]+4');
  7091. mq.keystroke('Left Left Left Backspace');
  7092. assertLatex('+4');
  7093. });
  7094. test('rendering mismatched brackets 1+(2+3]+4 from LaTeX then backspacing close-bracket then open-paren', function() {
  7095. mq.latex('1+\\left(2+3\\right]+4');
  7096. assertLatex('1+\\left(2+3\\right]+4');
  7097. mq.keystroke('Left Left Backspace');
  7098. assertLatex('1+\\left(2+3+4\\right)');
  7099. mq.keystroke('Left Left Left Backspace');
  7100. assertLatex('1+2+3+4');
  7101. });
  7102. test('rendering mismatched brackets 1+(2+3]+4 from LaTeX then backspacing open-paren', function() {
  7103. mq.latex('1+\\left(2+3\\right]+4');
  7104. assertLatex('1+\\left(2+3\\right]+4');
  7105. mq.keystroke('Left Left Left Left Left Left Backspace');
  7106. assertLatex('1+2+3+4');
  7107. });
  7108. test('rendering paren group 1+(2+3)+4 from LaTeX then backspacing close-paren then open-paren', function() {
  7109. mq.latex('1+\\left(2+3\\right)+4');
  7110. assertLatex('1+\\left(2+3\\right)+4');
  7111. mq.keystroke('Left Left Backspace');
  7112. assertLatex('1+\\left(2+3+4\\right)');
  7113. mq.keystroke('Left Left Left Backspace');
  7114. assertLatex('1+2+3+4');
  7115. });
  7116. test('rendering paren group 1+(2+3)+4 from LaTeX then backspacing open-paren', function() {
  7117. mq.latex('1+\\left(2+3\\right)+4');
  7118. assertLatex('1+\\left(2+3\\right)+4');
  7119. mq.keystroke('Left Left Left Left Left Left Backspace');
  7120. assertLatex('1+2+3+4');
  7121. });
  7122. test('wrapping selection in parens 1+(2+3)+4 then backspacing close-paren then open-paren', function() {
  7123. mq.typedText('1+2+3+4');
  7124. assertLatex('1+2+3+4');
  7125. mq.keystroke('Left Left Shift-Left Shift-Left Shift-Left').typedText(')');
  7126. assertLatex('1+\\left(2+3\\right)+4');
  7127. mq.keystroke('Backspace');
  7128. assertLatex('1+\\left(2+3+4\\right)');
  7129. mq.keystroke('Left Left Left Backspace');
  7130. assertLatex('1+2+3+4');
  7131. });
  7132. test('wrapping selection in parens 1+(2+3)+4 then backspacing open-paren', function() {
  7133. mq.typedText('1+2+3+4');
  7134. assertLatex('1+2+3+4');
  7135. mq.keystroke('Left Left Shift-Left Shift-Left Shift-Left').typedText('(');
  7136. assertLatex('1+\\left(2+3\\right)+4');
  7137. mq.keystroke('Backspace');
  7138. assertLatex('1+2+3+4');
  7139. });
  7140. test('backspacing close-bracket of 1+(2+3] (nothing after) then typing', function() {
  7141. mq.typedText('1+(2+3]');
  7142. assertLatex('1+\\left(2+3\\right]');
  7143. mq.keystroke('Backspace');
  7144. assertLatex('1+\\left(2+3\\right)');
  7145. mq.typedText('+4');
  7146. assertLatex('1+\\left(2+3+4\\right)');
  7147. });
  7148. test('backspacing open-paren of (2+3]+4 (nothing before) then typing', function() {
  7149. mq.typedText('(2+3]+4');
  7150. assertLatex('\\left(2+3\\right]+4');
  7151. mq.keystroke('Home Right Backspace');
  7152. assertLatex('2+3+4');
  7153. mq.typedText('1+');
  7154. assertLatex('1+2+3+4');
  7155. });
  7156. test('backspacing paren containing a one-sided paren 0+[(1+2)+3]+4', function() {
  7157. mq.typedText('0+[1+2+3]+4');
  7158. assertLatex('0+\\left[1+2+3\\right]+4');
  7159. mq.keystroke('Left Left Left Left Left').typedText(')');
  7160. assertLatex('0+\\left[\\left(1+2\\right)+3\\right]+4');
  7161. mq.keystroke('Right Right Right Backspace');
  7162. assertLatex('0+\\left[1+2\\right)+3+4');
  7163. });
  7164. test('backspacing paren inside a one-sided paren (0+[1+2]+3)+4', function() {
  7165. mq.typedText('0+[1+2]+3)+4');
  7166. assertLatex('\\left(0+\\left[1+2\\right]+3\\right)+4');
  7167. mq.keystroke('Left Left Left Left Left Backspace');
  7168. assertLatex('0+\\left[1+2+3\\right)+4');
  7169. });
  7170. test('backspacing paren containing and inside a one-sided paren (([1+2]))', function() {
  7171. mq.typedText('(1+2))');
  7172. assertLatex('\\left(\\left(1+2\\right)\\right)');
  7173. mq.keystroke('Left Left').typedText(']');
  7174. assertLatex('\\left(\\left(\\left[1+2\\right]\\right)\\right)');
  7175. mq.keystroke('Right Backspace');
  7176. assertLatex('\\left(\\left(1+2\\right]\\right)');
  7177. mq.keystroke('Backspace');
  7178. assertLatex('\\left(1+2\\right)');
  7179. });
  7180. test('auto-expanding calls .siblingCreated() on new siblings 1+((2+3))', function() {
  7181. mq.typedText('1+((2+3))');
  7182. assertLatex('1+\\left(\\left(2+3\\right)\\right)');
  7183. mq.keystroke('Left Left Left Left Left Left Del');
  7184. assertLatex('1+\\left(\\left(2+3\\right)\\right)');
  7185. mq.keystroke('Left Left Del');
  7186. assertLatex('\\left(1+\\left(2+3\\right)\\right)');
  7187. // now check that the inner open-paren isn't still a ghost
  7188. mq.keystroke('Right Right Right Right Del');
  7189. assertLatex('1+\\left(2+3\\right)');
  7190. });
  7191. test('that unwrapping calls .siblingCreated() on new siblings ((1+2)+(3+4))+5', function() {
  7192. mq.typedText('(1+2+3+4)+5');
  7193. assertLatex('\\left(1+2+3+4\\right)+5');
  7194. mq.keystroke('Home Right Right Right Right').typedText(')');
  7195. assertLatex('\\left(\\left(1+2\\right)+3+4\\right)+5');
  7196. mq.keystroke('Right').typedText('(');
  7197. assertLatex('\\left(\\left(1+2\\right)+\\left(3+4\\right)\\right)+5');
  7198. mq.keystroke('Right Right Right Right Right Backspace');
  7199. assertLatex('\\left(1+2\\right)+\\left(3+4\\right)+5');
  7200. mq.keystroke('Left Left Left Left Backspace');
  7201. assertLatex('\\left(1+2\\right)+3+4+5');
  7202. });
  7203. suite('pipes', function() {
  7204. test('typing then backspacing a pipe in the middle of 1+2+3+4', function() {
  7205. mq.typedText('1+2+3+4');
  7206. assertLatex('1+2+3+4');
  7207. mq.keystroke('Left Left Left').typedText('|');
  7208. assertLatex('1+2+\\left|3+4\\right|');
  7209. mq.keystroke('Backspace');
  7210. assertLatex('1+2+3+4');
  7211. });
  7212. test('backspacing close-pipe then open-pipe of 1+|2+3|+4', function() {
  7213. mq.typedText('1+|2+3|+4');
  7214. assertLatex('1+\\left|2+3\\right|+4');
  7215. mq.keystroke('Left Left Backspace');
  7216. assertLatex('1+\\left|2+3+4\\right|');
  7217. mq.keystroke('Left Left Left Backspace');
  7218. assertLatex('1+2+3+4');
  7219. });
  7220. test('backspacing open-pipe of 1+|2+3|+4', function() {
  7221. mq.typedText('1+|2+3|+4');
  7222. assertLatex('1+\\left|2+3\\right|+4');
  7223. mq.keystroke('Left Left Left Left Left Left Backspace');
  7224. assertLatex('1+2+3+4');
  7225. });
  7226. test('backspacing close-pipe then open-pipe of 1+|2+3| (nothing after pipe pair)', function() {
  7227. mq.typedText('1+|2+3|');
  7228. assertLatex('1+\\left|2+3\\right|');
  7229. mq.keystroke('Backspace');
  7230. assertLatex('1+\\left|2+3\\right|');
  7231. mq.keystroke('Left Left Left Backspace');
  7232. assertLatex('1+2+3');
  7233. });
  7234. test('backspacing open-pipe of 1+|2+3| (nothing after pipe pair)', function() {
  7235. mq.typedText('1+|2+3|');
  7236. assertLatex('1+\\left|2+3\\right|');
  7237. mq.keystroke('Left Left Left Left Backspace');
  7238. assertLatex('1+2+3');
  7239. });
  7240. test('backspacing close-pipe then open-pipe of |2+3|+4 (nothing before pipe pair)', function() {
  7241. mq.typedText('|2+3|+4');
  7242. assertLatex('\\left|2+3\\right|+4');
  7243. mq.keystroke('Left Left Backspace');
  7244. assertLatex('\\left|2+3+4\\right|');
  7245. mq.keystroke('Left Left Left Backspace');
  7246. assertLatex('2+3+4');
  7247. });
  7248. test('backspacing open-pipe of |2+3|+4 (nothing before pipe pair)', function() {
  7249. mq.typedText('|2+3|+4');
  7250. assertLatex('\\left|2+3\\right|+4');
  7251. mq.keystroke('Left Left Left Left Left Left Backspace');
  7252. assertLatex('2+3+4');
  7253. });
  7254. function assertParenBlockNonEmpty() {
  7255. var parenBlock = $(mq.el()).find('.mq-paren+span');
  7256. assert.equal(parenBlock.length, 1, 'exactly 1 paren block');
  7257. assert.ok(!parenBlock.hasClass('mq-empty'),
  7258. 'paren block auto-expanded, should no longer be gray');
  7259. }
  7260. test('backspacing close-pipe then open-pipe of 1+||+4 (empty pipe pair)', function() {
  7261. mq.typedText('1+||+4');
  7262. assertLatex('1+\\left|\\right|+4');
  7263. mq.keystroke('Left Left Backspace');
  7264. assertLatex('1+\\left|+4\\right|');
  7265. assertParenBlockNonEmpty();
  7266. mq.keystroke('Backspace');
  7267. assertLatex('1++4');
  7268. });
  7269. test('backspacing open-pipe of 1+||+4 (empty pipe pair)', function() {
  7270. mq.typedText('1+||+4');
  7271. assertLatex('1+\\left|\\right|+4');
  7272. mq.keystroke('Left Left Left Backspace');
  7273. assertLatex('1++4');
  7274. });
  7275. test('backspacing close-pipe then open-pipe of 1+|| (empty pipe pair, nothing after)', function() {
  7276. mq.typedText('1+||');
  7277. assertLatex('1+\\left|\\right|');
  7278. mq.keystroke('Backspace');
  7279. assertLatex('1+\\left|\\right|');
  7280. mq.keystroke('Backspace');
  7281. assertLatex('1+');
  7282. });
  7283. test('backspacing open-pipe of 1+|| (empty pipe pair, nothing after)', function() {
  7284. mq.typedText('1+||');
  7285. assertLatex('1+\\left|\\right|');
  7286. mq.keystroke('Left Backspace');
  7287. assertLatex('1+');
  7288. });
  7289. test('backspacing close-pipe then open-pipe of ||+4 (empty pipe pair, nothing before)', function() {
  7290. mq.typedText('||+4');
  7291. assertLatex('\\left|\\right|+4');
  7292. mq.keystroke('Left Left Backspace');
  7293. assertLatex('\\left|+4\\right|');
  7294. assertParenBlockNonEmpty();
  7295. mq.keystroke('Backspace');
  7296. assertLatex('+4');
  7297. });
  7298. test('backspacing open-pipe of ||+4 (empty pipe pair, nothing before)', function() {
  7299. mq.typedText('||+4');
  7300. assertLatex('\\left|\\right|+4');
  7301. mq.keystroke('Left Left Left Backspace');
  7302. assertLatex('+4');
  7303. });
  7304. test('rendering pipe pair 1+|2+3|+4 from LaTeX then backspacing close-pipe then open-pipe', function() {
  7305. mq.latex('1+\\left|2+3\\right|+4');
  7306. assertLatex('1+\\left|2+3\\right|+4');
  7307. mq.keystroke('Left Left Backspace');
  7308. assertLatex('1+\\left|2+3+4\\right|');
  7309. mq.keystroke('Left Left Left Backspace');
  7310. assertLatex('1+2+3+4');
  7311. });
  7312. test('rendering pipe pair 1+|2+3|+4 from LaTeX then backspacing open-pipe', function() {
  7313. mq.latex('1+\\left|2+3\\right|+4');
  7314. assertLatex('1+\\left|2+3\\right|+4');
  7315. mq.keystroke('Left Left Left Left Left Left Backspace');
  7316. assertLatex('1+2+3+4');
  7317. });
  7318. test('rendering mismatched paren/pipe group 1+|2+3)+4 from LaTeX then backspacing close-paren then open-pipe', function() {
  7319. mq.latex('1+\\left|2+3\\right)+4');
  7320. assertLatex('1+\\left|2+3\\right)+4');
  7321. mq.keystroke('Left Left Backspace');
  7322. assertLatex('1+\\left|2+3+4\\right|');
  7323. mq.keystroke('Left Left Left Backspace');
  7324. assertLatex('1+2+3+4');
  7325. });
  7326. test('rendering mismatched paren/pipe group 1+|2+3)+4 from LaTeX then backspacing open-pipe', function() {
  7327. mq.latex('1+\\left|2+3\\right)+4');
  7328. assertLatex('1+\\left|2+3\\right)+4');
  7329. mq.keystroke('Left Left Left Left Left Left Backspace');
  7330. assertLatex('1+2+3+4');
  7331. });
  7332. test('rendering mismatched paren/pipe group 1+(2+3|+4 from LaTeX then backspacing close-pipe then open-paren', function() {
  7333. mq.latex('1+\\left(2+3\\right|+4');
  7334. assertLatex('1+\\left(2+3\\right|+4');
  7335. mq.keystroke('Left Left Backspace');
  7336. assertLatex('1+\\left(2+3+4\\right)');
  7337. mq.keystroke('Left Left Left Backspace');
  7338. assertLatex('1+2+3+4');
  7339. });
  7340. test('rendering mismatched paren/pipe group 1+(2+3|+4 from LaTeX then backspacing open-paren', function() {
  7341. mq.latex('1+\\left(2+3\\right|+4');
  7342. assertLatex('1+\\left(2+3\\right|+4');
  7343. mq.keystroke('Left Left Left Left Left Left Backspace');
  7344. assertLatex('1+2+3+4');
  7345. });
  7346. test('wrapping selection in pipes 1+|2+3|+4 then backspacing open-pipe', function() {
  7347. mq.typedText('1+2+3+4');
  7348. assertLatex('1+2+3+4');
  7349. mq.keystroke('Left Left Shift-Left Shift-Left Shift-Left').typedText('|');
  7350. assertLatex('1+\\left|2+3\\right|+4');
  7351. mq.keystroke('Backspace');
  7352. assertLatex('1+2+3+4');
  7353. });
  7354. test('wrapping selection in pipes 1+|2+3|+4 then backspacing close-pipe then open-pipe', function() {
  7355. mq.typedText('1+2+3+4');
  7356. assertLatex('1+2+3+4');
  7357. mq.keystroke('Left Left Shift-Left Shift-Left Shift-Left').typedText('|');
  7358. assertLatex('1+\\left|2+3\\right|+4');
  7359. mq.keystroke('Tab Backspace');
  7360. assertLatex('1+\\left|2+3+4\\right|');
  7361. mq.keystroke('Left Left Left Backspace');
  7362. assertLatex('1+2+3+4');
  7363. });
  7364. test('backspacing close-pipe of 1+|2+3| (nothing after) then typing', function() {
  7365. mq.typedText('1+|2+3|');
  7366. assertLatex('1+\\left|2+3\\right|');
  7367. mq.keystroke('Backspace');
  7368. assertLatex('1+\\left|2+3\\right|');
  7369. mq.typedText('+4');
  7370. assertLatex('1+\\left|2+3+4\\right|');
  7371. });
  7372. test('backspacing open-pipe of |2+3|+4 (nothing before) then typing', function() {
  7373. mq.typedText('|2+3|+4');
  7374. assertLatex('\\left|2+3\\right|+4');
  7375. mq.keystroke('Home Right Backspace');
  7376. assertLatex('2+3+4');
  7377. mq.typedText('1+');
  7378. assertLatex('1+2+3+4');
  7379. });
  7380. test('backspacing pipe containing a one-sided pipe 0+|1+|2+3||+4', function() {
  7381. mq.typedText('0+|1+2+3|+4');
  7382. assertLatex('0+\\left|1+2+3\\right|+4');
  7383. mq.keystroke('Left Left Left Left Left Left').typedText('|');
  7384. assertLatex('0+\\left|1+\\left|2+3\\right|\\right|+4');
  7385. mq.keystroke('Shift-Tab Shift-Tab Del');
  7386. assertLatex('0+1+\\left|2+3\\right|+4');
  7387. });
  7388. test('backspacing pipe inside a one-sided pipe 0+|1+|2+3|+4|', function() {
  7389. mq.typedText('0+1+|2+3|+4');
  7390. assertLatex('0+1+\\left|2+3\\right|+4');
  7391. mq.keystroke('Home Right Right').typedText('|');
  7392. assertLatex('0+\\left|1+\\left|2+3\\right|+4\\right|');
  7393. mq.keystroke('Right Right Del');
  7394. assertLatex('0+\\left|1+2+3\\right|+4');
  7395. });
  7396. test('backspacing pipe containing and inside a one-sided pipe |0+|1+|2+3||+4|', function() {
  7397. mq.typedText('0+|1+2+3|+4');
  7398. assertLatex('0+\\left|1+2+3\\right|+4');
  7399. mq.keystroke('Home').typedText('|');
  7400. assertLatex('\\left|0+\\left|1+2+3\\right|+4\\right|');
  7401. mq.keystroke('Right Right Right Right Right').typedText('|');
  7402. assertLatex('\\left|0+\\left|1+\\left|2+3\\right|\\right|+4\\right|');
  7403. mq.keystroke('Left Left Left Backspace');
  7404. assertLatex('\\left|0+1+\\left|2+3\\right|+4\\right|');
  7405. });
  7406. test('backspacing pipe containing a one-sided pipe facing same way 0+||1+2||+3', function() {
  7407. mq.typedText('0+|1+2|+3');
  7408. assertLatex('0+\\left|1+2\\right|+3');
  7409. mq.keystroke('Home Right Right Right').typedText('|');
  7410. assertLatex('0+\\left|\\left|1+2\\right|\\right|+3');
  7411. mq.keystroke('Tab Tab Backspace');
  7412. assertLatex('0+\\left|\\left|1+2\\right|+3\\right|');
  7413. });
  7414. test('backspacing pipe inside a one-sided pipe facing same way 0+|1+|2+3|+4|', function() {
  7415. mq.typedText('0+1+|2+3|+4');
  7416. assertLatex('0+1+\\left|2+3\\right|+4');
  7417. mq.keystroke('Home Right Right').typedText('|');
  7418. assertLatex('0+\\left|1+\\left|2+3\\right|+4\\right|');
  7419. mq.keystroke('Right Right Right Right Right Right Right Backspace');
  7420. assertLatex('0+\\left|1+\\left|2+3+4\\right|\\right|');
  7421. });
  7422. test('backspacing open-paren of mismatched paren/pipe group containing a one-sided pipe 0+(1+|2+3||+4', function() {
  7423. mq.latex('0+\\left(1+2+3\\right|+4');
  7424. assertLatex('0+\\left(1+2+3\\right|+4');
  7425. mq.keystroke('Left Left Left Left Left Left').typedText('|');
  7426. assertLatex('0+\\left(1+\\left|2+3\\right|\\right|+4');
  7427. mq.keystroke('Shift-Tab Shift-Tab Del');
  7428. assertLatex('0+1+\\left|2+3\\right|+4');
  7429. });
  7430. test('backspacing open-paren of mismatched paren/pipe group inside a one-sided pipe 0+|1+(2+3|+4|', function() {
  7431. mq.latex('0+1+\\left(2+3\\right|+4');
  7432. assertLatex('0+1+\\left(2+3\\right|+4');
  7433. mq.keystroke('Home Right Right').typedText('|');
  7434. assertLatex('0+\\left|1+\\left(2+3\\right|+4\\right|');
  7435. mq.keystroke('Right Right Del');
  7436. assertLatex('0+\\left|1+2+3\\right|+4');
  7437. });
  7438. });
  7439. }
  7440. suite('typing outside ghost paren', function() {
  7441. test('typing outside ghost paren solidifies ghost 1+(2+3)', function() {
  7442. mq.typedText('1+(2+3');
  7443. assertLatex('1+\\left(2+3\\right)');
  7444. mq.keystroke('Right').typedText('+4');
  7445. assertLatex('1+\\left(2+3\\right)+4');
  7446. mq.keystroke('Left Left Left Left Left Left Left Del');
  7447. assertLatex('\\left(1+2+3\\right)+4');
  7448. });
  7449. test('selected and replaced by LiveFraction solidifies ghosts (1+2)/( )', function() {
  7450. mq.typedText('1+2)/');
  7451. assertLatex('\\frac{\\left(1+2\\right)}{ }');
  7452. mq.keystroke('Left Backspace');
  7453. assertLatex('\\frac{\\left(1+2\\right)}{ }');
  7454. });
  7455. test('close paren group by typing close-bracket outside ghost paren (1+2]', function() {
  7456. mq.typedText('(1+2');
  7457. assertLatex('\\left(1+2\\right)');
  7458. mq.keystroke('Right').typedText(']');
  7459. assertLatex('\\left(1+2\\right]');
  7460. });
  7461. test('close adjacent paren group before containing paren group (1+(2+3])', function() {
  7462. mq.typedText('(1+(2+3');
  7463. assertLatex('\\left(1+\\left(2+3\\right)\\right)');
  7464. mq.keystroke('Right').typedText(']');
  7465. assertLatex('\\left(1+\\left(2+3\\right]\\right)');
  7466. mq.typedText(']');
  7467. assertLatex('\\left(1+\\left(2+3\\right]\\right]');
  7468. });
  7469. test('can type close-bracket on solid side of one-sided paren [](1+2)', function() {
  7470. mq.typedText('(1+2');
  7471. assertLatex('\\left(1+2\\right)');
  7472. mq.moveToLeftEnd().typedText(']');
  7473. assertLatex('\\left[\\right]\\left(1+2\\right)');
  7474. });
  7475. suite('pipes', function() {
  7476. test('close pipe pair from outside to the right |1+2|', function() {
  7477. mq.typedText('|1+2');
  7478. assertLatex('\\left|1+2\\right|');
  7479. mq.keystroke('Right').typedText('|');
  7480. assertLatex('\\left|1+2\\right|');
  7481. mq.keystroke('Home Del');
  7482. assertLatex('\\left|1+2\\right|');
  7483. });
  7484. test('close pipe pair from outside to the left |1+2|', function() {
  7485. mq.typedText('|1+2|');
  7486. assertLatex('\\left|1+2\\right|');
  7487. mq.keystroke('Home Del');
  7488. assertLatex('\\left|1+2\\right|');
  7489. mq.keystroke('Left').typedText('|');
  7490. assertLatex('\\left|1+2\\right|');
  7491. mq.keystroke('Ctrl-End Backspace');
  7492. assertLatex('\\left|1+2\\right|');
  7493. });
  7494. test('can type pipe on solid side of one-sided pipe ||||', function() {
  7495. mq.typedText('|');
  7496. assertLatex('\\left|\\right|');
  7497. mq.moveToLeftEnd().typedText('|');
  7498. assertLatex('\\left|\\left|\\right|\\right|');
  7499. });
  7500. });
  7501. });
  7502. });
  7503. suite('autoCommands', function() {
  7504. setup(function() {
  7505. MQ.config({
  7506. autoCommands: 'pi tau phi theta Gamma sum prod sqrt nthroot'
  7507. });
  7508. });
  7509. test('individual commands', function(){
  7510. mq.typedText('sum' + 'n=0');
  7511. mq.keystroke('Up').typedText('100').keystroke('Right');
  7512. assertLatex('\\sum_{n=0}^{100}');
  7513. mq.keystroke('Ctrl-Backspace');
  7514. mq.typedText('prod');
  7515. mq.typedText('n=0').keystroke('Up').typedText('100').keystroke('Right');
  7516. assertLatex('\\prod_{n=0}^{100}');
  7517. mq.keystroke('Ctrl-Backspace');
  7518. mq.typedText('sqrt');
  7519. mq.typedText('100').keystroke('Right');
  7520. assertLatex('\\sqrt{100}');
  7521. mq.keystroke('Ctrl-Backspace');
  7522. mq.typedText('nthroot');
  7523. mq.typedText('n').keystroke('Right').typedText('100').keystroke('Right');
  7524. assertLatex('\\sqrt[n]{100}');
  7525. mq.keystroke('Ctrl-Backspace');
  7526. mq.typedText('pi');
  7527. assertLatex('\\pi');
  7528. mq.keystroke('Backspace');
  7529. mq.typedText('tau');
  7530. assertLatex('\\tau');
  7531. mq.keystroke('Backspace');
  7532. mq.typedText('phi');
  7533. assertLatex('\\phi');
  7534. mq.keystroke('Backspace');
  7535. mq.typedText('theta');
  7536. assertLatex('\\theta');
  7537. mq.keystroke('Backspace');
  7538. mq.typedText('Gamma');
  7539. assertLatex('\\Gamma');
  7540. mq.keystroke('Backspace');
  7541. });
  7542. test('sequences of auto-commands and other assorted characters', function() {
  7543. mq.typedText('sin' + 'pi');
  7544. assertLatex('\\sin\\pi');
  7545. mq.keystroke('Left Backspace');
  7546. assertLatex('si\\pi');
  7547. mq.keystroke('Left').typedText('p');
  7548. assertLatex('spi\\pi');
  7549. mq.typedText('i');
  7550. assertLatex('s\\pi i\\pi');
  7551. mq.typedText('p');
  7552. assertLatex('s\\pi pi\\pi');
  7553. mq.keystroke('Right').typedText('n');
  7554. assertLatex('s\\pi pin\\pi');
  7555. mq.keystroke('Left Left Left').typedText('s');
  7556. assertLatex('s\\pi spin\\pi');
  7557. mq.keystroke('Backspace');
  7558. assertLatex('s\\pi pin\\pi');
  7559. mq.keystroke('Del').keystroke('Backspace');
  7560. assertLatex('\\sin\\pi');
  7561. });
  7562. test('command contains non-letters', function() {
  7563. assert.throws(function() { MQ.config({ autoCommands: 'e1' }); });
  7564. });
  7565. test('command length less than 2', function() {
  7566. assert.throws(function() { MQ.config({ autoCommands: 'e' }); });
  7567. });
  7568. test('command is a built-in operator name', function() {
  7569. var cmds = ('Pr arg deg det dim exp gcd hom inf ker lg lim ln log max min sup'
  7570. + ' limsup liminf injlim projlim Pr').split(' ');
  7571. for (var i = 0; i < cmds.length; i += 1) {
  7572. assert.throws(function() { MQ.config({ autoCommands: cmds[i] }) },
  7573. 'MQ.config({ autoCommands: "'+cmds[i]+'" })');
  7574. }
  7575. });
  7576. test('built-in operator names even after auto-operator names overridden', function() {
  7577. MQ.config({ autoOperatorNames: 'sin inf arcosh cosh cos cosec csc' });
  7578. // ^ happen to be the ones required by autoOperatorNames.test.js
  7579. var cmds = 'Pr arg deg det exp gcd inf lg lim ln log max min sup'.split(' ');
  7580. for (var i = 0; i < cmds.length; i += 1) {
  7581. assert.throws(function() { MQ.config({ autoCommands: cmds[i] }) },
  7582. 'MQ.config({ autoCommands: "'+cmds[i]+'" })');
  7583. }
  7584. });
  7585. suite('command list not perfectly space-delimited', function() {
  7586. test('double space', function() {
  7587. assert.throws(function() { MQ.config({ autoCommands: 'pi theta' }); });
  7588. });
  7589. test('leading space', function() {
  7590. assert.throws(function() { MQ.config({ autoCommands: ' pi' }); });
  7591. });
  7592. test('trailing space', function() {
  7593. assert.throws(function() { MQ.config({ autoCommands: 'pi ' }); });
  7594. });
  7595. });
  7596. });
  7597. suite('inequalities', function() {
  7598. // assertFullyFunctioningInequality() checks not only that the inequality
  7599. // has the right LaTeX and when you backspace it has the right LaTeX,
  7600. // but also that when you backspace you get the right state such that
  7601. // you can either type = again to get the non-strict inequality again,
  7602. // or backspace again and it'll delete correctly.
  7603. function assertFullyFunctioningInequality(nonStrict, strict) {
  7604. assertLatex(nonStrict);
  7605. mq.keystroke('Backspace');
  7606. assertLatex(strict);
  7607. mq.typedText('=');
  7608. assertLatex(nonStrict);
  7609. mq.keystroke('Backspace');
  7610. assertLatex(strict);
  7611. mq.keystroke('Backspace');
  7612. assertLatex('');
  7613. }
  7614. test('typing and backspacing <= and >=', function() {
  7615. mq.typedText('<');
  7616. assertLatex('<');
  7617. mq.typedText('=');
  7618. assertFullyFunctioningInequality('\\le', '<');
  7619. mq.typedText('>');
  7620. assertLatex('>');
  7621. mq.typedText('=');
  7622. assertFullyFunctioningInequality('\\ge', '>');
  7623. mq.typedText('<<>>==>><<==');
  7624. assertLatex('<<>\\ge=>><\\le=');
  7625. });
  7626. test('typing ≤ and ≥ chars directly', function() {
  7627. mq.typedText('≤');
  7628. assertFullyFunctioningInequality('\\le', '<');
  7629. mq.typedText('≥');
  7630. assertFullyFunctioningInequality('\\ge', '>');
  7631. });
  7632. suite('rendered from LaTeX', function() {
  7633. test('control sequences', function() {
  7634. mq.latex('\\le');
  7635. assertFullyFunctioningInequality('\\le', '<');
  7636. mq.latex('\\ge');
  7637. assertFullyFunctioningInequality('\\ge', '>');
  7638. });
  7639. test('≤ and ≥ chars', function() {
  7640. mq.latex('≤');
  7641. assertFullyFunctioningInequality('\\le', '<');
  7642. mq.latex('≥');
  7643. assertFullyFunctioningInequality('\\ge', '>');
  7644. });
  7645. });
  7646. });
  7647. suite('SupSub behavior options', function() {
  7648. test('charsThatBreakOutOfSupSub', function() {
  7649. assert.equal(mq.typedText('x^2n+y').latex(), 'x^{2n+y}');
  7650. mq.latex('');
  7651. assert.equal(mq.typedText('x^+2n').latex(), 'x^{+2n}');
  7652. mq.latex('');
  7653. assert.equal(mq.typedText('x^-2n').latex(), 'x^{-2n}');
  7654. mq.latex('');
  7655. assert.equal(mq.typedText('x^=2n').latex(), 'x^{=2n}');
  7656. mq.latex('');
  7657. MQ.config({ charsThatBreakOutOfSupSub: '+-=<>' });
  7658. assert.equal(mq.typedText('x^2n+y').latex(), 'x^{2n}+y');
  7659. mq.latex('');
  7660. // Unary operators never break out of exponents.
  7661. assert.equal(mq.typedText('x^+2n').latex(), 'x^{+2n}');
  7662. mq.latex('');
  7663. assert.equal(mq.typedText('x^-2n').latex(), 'x^{-2n}');
  7664. mq.latex('');
  7665. assert.equal(mq.typedText('x^=2n').latex(), 'x^{=2n}');
  7666. mq.latex('');
  7667. // Only break out of exponents if cursor at the end, don't
  7668. // jump from the middle of the exponent out to the right.
  7669. assert.equal(mq.typedText('x^ab').latex(), 'x^{ab}');
  7670. assert.equal(mq.keystroke('Left').typedText('+').latex(), 'x^{a+b}');
  7671. mq.latex('');
  7672. });
  7673. test('supSubsRequireOperand', function() {
  7674. assert.equal(mq.typedText('^').latex(), '^{ }');
  7675. assert.equal(mq.typedText('2').latex(), '^2');
  7676. assert.equal(mq.typedText('n').latex(), '^{2n}');
  7677. mq.latex('');
  7678. assert.equal(mq.typedText('x').latex(), 'x');
  7679. assert.equal(mq.typedText('^').latex(), 'x^{ }');
  7680. assert.equal(mq.typedText('2').latex(), 'x^2');
  7681. assert.equal(mq.typedText('n').latex(), 'x^{2n}');
  7682. mq.latex('');
  7683. assert.equal(mq.typedText('x').latex(), 'x');
  7684. assert.equal(mq.typedText('^').latex(), 'x^{ }');
  7685. assert.equal(mq.typedText('^').latex(), 'x^{^{ }}');
  7686. assert.equal(mq.typedText('2').latex(), 'x^{^2}');
  7687. assert.equal(mq.typedText('n').latex(), 'x^{^{2n}}');
  7688. mq.latex('');
  7689. MQ.config({ supSubsRequireOperand: true });
  7690. assert.equal(mq.typedText('^').latex(), '');
  7691. assert.equal(mq.typedText('2').latex(), '2');
  7692. assert.equal(mq.typedText('n').latex(), '2n');
  7693. mq.latex('');
  7694. assert.equal(mq.typedText('x').latex(), 'x');
  7695. assert.equal(mq.typedText('^').latex(), 'x^{ }');
  7696. assert.equal(mq.typedText('2').latex(), 'x^2');
  7697. assert.equal(mq.typedText('n').latex(), 'x^{2n}');
  7698. mq.latex('');
  7699. assert.equal(mq.typedText('x').latex(), 'x');
  7700. assert.equal(mq.typedText('^').latex(), 'x^{ }');
  7701. assert.equal(mq.typedText('^').latex(), 'x^{ }');
  7702. assert.equal(mq.typedText('2').latex(), 'x^2');
  7703. assert.equal(mq.typedText('n').latex(), 'x^{2n}');
  7704. });
  7705. });
  7706. });
  7707. suite('up/down', function() {
  7708. var mq, rootBlock, controller, cursor;
  7709. setup(function() {
  7710. mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
  7711. rootBlock = mq.__controller.root;
  7712. controller = mq.__controller;
  7713. cursor = controller.cursor;
  7714. });
  7715. teardown(function() {
  7716. $(mq.el()).remove();
  7717. });
  7718. test('up/down in out of exponent', function() {
  7719. controller.renderLatexMath('x^{nm}');
  7720. var exp = rootBlock.ends[R],
  7721. expBlock = exp.ends[L];
  7722. assert.equal(exp.latex(), '^{nm}', 'right end el is exponent');
  7723. assert.equal(cursor.parent, rootBlock, 'cursor is in root block');
  7724. assert.equal(cursor[L], exp, 'cursor is at the end of root block');
  7725. mq.keystroke('Up');
  7726. assert.equal(cursor.parent, expBlock, 'cursor up goes into exponent');
  7727. mq.keystroke('Down');
  7728. assert.equal(cursor.parent, rootBlock, 'cursor down leaves exponent');
  7729. assert.equal(cursor[L], exp, 'down when cursor at end of exponent puts cursor after exponent');
  7730. mq.keystroke('Up Left Left');
  7731. assert.equal(cursor.parent, expBlock, 'cursor up left stays in exponent');
  7732. assert.equal(cursor[L], 0, 'cursor is at the beginning of exponent');
  7733. mq.keystroke('Down');
  7734. assert.equal(cursor.parent, rootBlock, 'cursor down leaves exponent');
  7735. assert.equal(cursor[R], exp, 'cursor down in beginning of exponent puts cursor before exponent');
  7736. mq.keystroke('Up Right');
  7737. assert.equal(cursor.parent, expBlock, 'cursor up left stays in exponent');
  7738. assert.equal(cursor[L].latex(), 'n', 'cursor is in the middle of exponent');
  7739. assert.equal(cursor[R].latex(), 'm', 'cursor is in the middle of exponent');
  7740. mq.keystroke('Down');
  7741. assert.equal(cursor.parent, rootBlock, 'cursor down leaves exponent');
  7742. assert.equal(cursor[R], exp, 'cursor down in middle of exponent puts cursor before exponent');
  7743. });
  7744. // literally just swapped up and down, exponent with subscript, nm with 12
  7745. test('up/down in out of subscript', function() {
  7746. controller.renderLatexMath('a_{12}');
  7747. var sub = rootBlock.ends[R],
  7748. subBlock = sub.ends[L];
  7749. assert.equal(sub.latex(), '_{12}', 'right end el is subscript');
  7750. assert.equal(cursor.parent, rootBlock, 'cursor is in root block');
  7751. assert.equal(cursor[L], sub, 'cursor is at the end of root block');
  7752. mq.keystroke('Down');
  7753. assert.equal(cursor.parent, subBlock, 'cursor down goes into subscript');
  7754. mq.keystroke('Up');
  7755. assert.equal(cursor.parent, rootBlock, 'cursor up leaves subscript');
  7756. assert.equal(cursor[L], sub, 'up when cursor at end of subscript puts cursor after subscript');
  7757. mq.keystroke('Down Left Left');
  7758. assert.equal(cursor.parent, subBlock, 'cursor down left stays in subscript');
  7759. assert.equal(cursor[L], 0, 'cursor is at the beginning of subscript');
  7760. mq.keystroke('Up');
  7761. assert.equal(cursor.parent, rootBlock, 'cursor up leaves subscript');
  7762. assert.equal(cursor[R], sub, 'cursor up in beginning of subscript puts cursor before subscript');
  7763. mq.keystroke('Down Right');
  7764. assert.equal(cursor.parent, subBlock, 'cursor down left stays in subscript');
  7765. assert.equal(cursor[L].latex(), '1', 'cursor is in the middle of subscript');
  7766. assert.equal(cursor[R].latex(), '2', 'cursor is in the middle of subscript');
  7767. mq.keystroke('Up');
  7768. assert.equal(cursor.parent, rootBlock, 'cursor up leaves subscript');
  7769. assert.equal(cursor[R], sub, 'cursor up in middle of subscript puts cursor before subscript');
  7770. });
  7771. test('up/down into and within fraction', function() {
  7772. controller.renderLatexMath('\\frac{12}{34}');
  7773. var frac = rootBlock.ends[L],
  7774. numer = frac.ends[L],
  7775. denom = frac.ends[R];
  7776. assert.equal(frac.latex(), '\\frac{12}{34}', 'fraction is in root block');
  7777. assert.equal(frac, rootBlock.ends[R], 'fraction is sole child of root block');
  7778. assert.equal(numer.latex(), '12', 'numerator is left end child of fraction');
  7779. assert.equal(denom.latex(), '34', 'denominator is right end child of fraction');
  7780. mq.keystroke('Up');
  7781. assert.equal(cursor.parent, numer, 'cursor up goes into numerator');
  7782. assert.equal(cursor[R], 0, 'cursor up from right of fraction inserts at right end of numerator');
  7783. mq.keystroke('Down');
  7784. assert.equal(cursor.parent, denom, 'cursor down goes into denominator');
  7785. assert.equal(cursor[R], 0, 'cursor down from numerator inserts at right end of denominator');
  7786. mq.keystroke('Up');
  7787. assert.equal(cursor.parent, numer, 'cursor up goes into numerator');
  7788. assert.equal(cursor[R], 0, 'cursor up from denominator inserts at right end of numerator');
  7789. mq.keystroke('Left Left Left');
  7790. assert.equal(cursor.parent, rootBlock, 'cursor outside fraction');
  7791. assert.equal(cursor[R], frac, 'cursor before fraction');
  7792. mq.keystroke('Up');
  7793. assert.equal(cursor.parent, numer, 'cursor up goes into numerator');
  7794. assert.equal(cursor[L], 0, 'cursor up from left of fraction inserts at left end of numerator');
  7795. mq.keystroke('Left');
  7796. assert.equal(cursor.parent, rootBlock, 'cursor outside fraction');
  7797. assert.equal(cursor[R], frac, 'cursor before fraction');
  7798. mq.keystroke('Down');
  7799. assert.equal(cursor.parent, denom, 'cursor down goes into denominator');
  7800. assert.equal(cursor[L], 0, 'cursor down from left of fraction inserts at left end of denominator');
  7801. });
  7802. test('nested subscripts and fractions', function() {
  7803. controller.renderLatexMath('\\frac{d}{dx_{\\frac{24}{36}0}}\\sqrt{x}=x^{\\frac{1}{2}}');
  7804. var exp = rootBlock.ends[R],
  7805. expBlock = exp.ends[L],
  7806. half = expBlock.ends[L],
  7807. halfNumer = half.ends[L],
  7808. halfDenom = half.ends[R];
  7809. mq.keystroke('Left');
  7810. assert.equal(cursor.parent, expBlock, 'cursor left goes into exponent');
  7811. mq.keystroke('Down');
  7812. assert.equal(cursor.parent, halfDenom, 'cursor down goes into denominator of half');
  7813. mq.keystroke('Down');
  7814. assert.equal(cursor.parent, rootBlock, 'down again puts cursor back in root block');
  7815. assert.equal(cursor[L], exp, 'down from end of half puts cursor after exponent');
  7816. var derivative = rootBlock.ends[L],
  7817. dBlock = derivative.ends[L],
  7818. dxBlock = derivative.ends[R],
  7819. sub = dxBlock.ends[R],
  7820. subBlock = sub.ends[L],
  7821. subFrac = subBlock.ends[L],
  7822. subFracNumer = subFrac.ends[L],
  7823. subFracDenom = subFrac.ends[R];
  7824. cursor.insAtLeftEnd(rootBlock);
  7825. mq.keystroke('Down Right Right Down');
  7826. assert.equal(cursor.parent, subBlock, 'cursor in subscript');
  7827. mq.keystroke('Up');
  7828. assert.equal(cursor.parent, subFracNumer, 'cursor up from beginning of subscript goes into subscript fraction numerator');
  7829. mq.keystroke('Up');
  7830. assert.equal(cursor.parent, dxBlock, 'cursor up from subscript fraction numerator goes out of subscript');
  7831. assert.equal(cursor[R], sub, 'cursor up from subscript fraction numerator goes before subscript');
  7832. mq.keystroke('Down Down');
  7833. assert.equal(cursor.parent, subFracDenom, 'cursor in subscript fraction denominator');
  7834. mq.keystroke('Up Up');
  7835. assert.equal(cursor.parent, dxBlock, 'cursor up up from subscript fraction denominator that\s not at right end goes out of subscript');
  7836. assert.equal(cursor[R], sub, 'cursor up up from subscript fraction denominator that\s not at right end goes before subscript');
  7837. cursor.insAtRightEnd(subBlock);
  7838. controller.backspace();
  7839. assert.equal(subFrac[R], 0, 'subscript fraction is at right end');
  7840. assert.equal(cursor[L], subFrac, 'cursor after subscript fraction');
  7841. mq.keystroke('Down');
  7842. assert.equal(cursor.parent, subFracDenom, 'cursor in subscript fraction denominator');
  7843. mq.keystroke('Up Up');
  7844. assert.equal(cursor.parent, dxBlock, 'cursor up up from subscript fraction denominator that is at right end goes out of subscript');
  7845. assert.equal(cursor[L], sub, 'cursor up up from subscript fraction denominator that is at right end goes after subscript');
  7846. });
  7847. test('\\MathQuillMathField{} in a fraction', function() {
  7848. var outer = MQ.MathField(
  7849. $('<span>\\frac{\\MathQuillMathField{n}}{2}</span>').appendTo('#mock')[0]
  7850. );
  7851. var inner = MQ($(outer.el()).find('.mq-editable-field')[0]);
  7852. assert.equal(inner.__controller.cursor.parent, inner.__controller.root);
  7853. inner.keystroke('Down');
  7854. assert.equal(inner.__controller.cursor.parent, inner.__controller.root);
  7855. $(outer.el()).remove();
  7856. });
  7857. });
  7858. var MQ1 = getInterface(1);
  7859. for (var key in MQ1) (function(key, val) {
  7860. if (typeof val === 'function') {
  7861. MathQuill[key] = function() {
  7862. insistOnInterVer();
  7863. return val.apply(this, arguments);
  7864. };
  7865. MathQuill[key].prototype = val.prototype;
  7866. }
  7867. else MathQuill[key] = val;
  7868. }(key, MQ1[key]));
  7869. }());