12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121 |
- /**
- * MathQuill v0.10.1 http://mathquill.com
- * by Han, Jeanine, and Mary maintainers@mathquill.com
- *
- * This Source Code Form is subject to the terms of the
- * Mozilla Public License, v. 2.0. If a copy of the MPL
- * was not distributed with this file, You can obtain
- * one at http://mozilla.org/MPL/2.0/.
- */
- (function() {
- var jQuery = window.jQuery,
- undefined,
- mqCmdId = 'mathquill-command-id',
- mqBlockId = 'mathquill-block-id',
- min = Math.min,
- max = Math.max;
- function noop() {}
- /**
- * A utility higher-order function that makes defining variadic
- * functions more convenient by letting you essentially define functions
- * with the last argument as a splat, i.e. the last argument "gathers up"
- * remaining arguments to the function:
- * var doStuff = variadic(function(first, rest) { return rest; });
- * doStuff(1, 2, 3); // => [2, 3]
- */
- var __slice = [].slice;
- function variadic(fn) {
- var numFixedArgs = fn.length - 1;
- return function() {
- var args = __slice.call(arguments, 0, numFixedArgs);
- var varArg = __slice.call(arguments, numFixedArgs);
- return fn.apply(this, args.concat([ varArg ]));
- };
- }
- /**
- * A utility higher-order function that makes combining object-oriented
- * programming and functional programming techniques more convenient:
- * given a method name and any number of arguments to be bound, returns
- * a function that calls it's first argument's method of that name (if
- * it exists) with the bound arguments and any additional arguments that
- * are passed:
- * var sendMethod = send('method', 1, 2);
- * var obj = { method: function() { return Array.apply(this, arguments); } };
- * sendMethod(obj, 3, 4); // => [1, 2, 3, 4]
- * // or more specifically,
- * var obj2 = { method: function(one, two, three) { return one*two + three; } };
- * sendMethod(obj2, 3); // => 5
- * sendMethod(obj2, 4); // => 6
- */
- var send = variadic(function(method, args) {
- return variadic(function(obj, moreArgs) {
- if (method in obj) return obj[method].apply(obj, args.concat(moreArgs));
- });
- });
- /**
- * A utility higher-order function that creates "implicit iterators"
- * from "generators": given a function that takes in a sole argument,
- * a "yield_" function, that calls "yield_" repeatedly with an object as
- * a sole argument (presumably objects being iterated over), returns
- * a function that calls it's first argument on each of those objects
- * (if the first argument is a function, it is called repeatedly with
- * each object as the first argument, otherwise it is stringified and
- * the method of that name is called on each object (if such a method
- * exists)), passing along all additional arguments:
- * var a = [
- * { method: function(list) { list.push(1); } },
- * { method: function(list) { list.push(2); } },
- * { method: function(list) { list.push(3); } }
- * ];
- * a.each = iterator(function(yield_) {
- * for (var i in this) yield_(this[i]);
- * });
- * var list = [];
- * a.each('method', list);
- * list; // => [1, 2, 3]
- * // Note that the for-in loop will yield 'each', but 'each' maps to
- * // the function object created by iterator() which does not have a
- * // .method() method, so that just fails silently.
- */
- function iterator(generator) {
- return variadic(function(fn, args) {
- if (typeof fn !== 'function') fn = send(fn);
- var yield_ = function(obj) { return fn.apply(obj, [ obj ].concat(args)); };
- return generator.call(this, yield_);
- });
- }
- /**
- * sugar to make defining lots of commands easier.
- * TODO: rethink this.
- */
- function bind(cons /*, args... */) {
- var args = __slice.call(arguments, 1);
- return function() {
- return cons.apply(this, args);
- };
- }
- /**
- * a development-only debug method. This definition and all
- * calls to `pray` will be stripped from the minified
- * build of mathquill.
- *
- * This function must be called by name to be removed
- * at compile time. Do not define another function
- * with the same name, and only call this function by
- * name.
- */
- function pray(message, cond) {
- if (!cond) throw new Error('prayer failed: '+message);
- }
- var P = (function(prototype, ownProperty, undefined) {
- // helper functions that also help minification
- function isObject(o) { return typeof o === 'object'; }
- function isFunction(f) { return typeof f === 'function'; }
- // used to extend the prototypes of superclasses (which might not
- // have `.Bare`s)
- function SuperclassBare() {}
- return function P(_superclass /* = Object */, definition) {
- // handle the case where no superclass is given
- if (definition === undefined) {
- definition = _superclass;
- _superclass = Object;
- }
- // C is the class to be returned.
- //
- // It delegates to instantiating an instance of `Bare`, so that it
- // will always return a new instance regardless of the calling
- // context.
- //
- // TODO: the Chrome inspector shows all created objects as `C`
- // rather than `Object`. Setting the .name property seems to
- // have no effect. Is there a way to override this behavior?
- function C() {
- var self = new Bare;
- if (isFunction(self.init)) self.init.apply(self, arguments);
- return self;
- }
- // C.Bare is a class with a noop constructor. Its prototype is the
- // same as C, so that instances of C.Bare are also instances of C.
- // New objects can be allocated without initialization by calling
- // `new MyClass.Bare`.
- function Bare() {}
- C.Bare = Bare;
- // Set up the prototype of the new class.
- var _super = SuperclassBare[prototype] = _superclass[prototype];
- var proto = Bare[prototype] = C[prototype] = C.p = new SuperclassBare;
- // other variables, as a minifier optimization
- var extensions;
- // set the constructor property on the prototype, for convenience
- proto.constructor = C;
- C.mixin = function(def) {
- Bare[prototype] = C[prototype] = P(C, def)[prototype];
- return C;
- }
- return (C.open = function(def) {
- extensions = {};
- if (isFunction(def)) {
- // call the defining function with all the arguments you need
- // extensions captures the return value.
- extensions = def.call(C, proto, _super, C, _superclass);
- }
- else if (isObject(def)) {
- // if you passed an object instead, we'll take it
- extensions = def;
- }
- // ...and extend it
- if (isObject(extensions)) {
- for (var ext in extensions) {
- if (ownProperty.call(extensions, ext)) {
- proto[ext] = extensions[ext];
- }
- }
- }
- // if there's no init, we assume we're inheriting a non-pjs class, so
- // we default to applying the superclass's constructor.
- if (!isFunction(proto.init)) {
- proto.init = _superclass;
- }
- return C;
- })(definition);
- }
- // as a minifier optimization, we've closured in a few helper functions
- // and the string 'prototype' (C[p] is much shorter than C.prototype)
- })('prototype', ({}).hasOwnProperty);
- /*************************************************
- * Base classes of edit tree-related objects
- *
- * Only doing tree node manipulation via these
- * adopt/ disown methods guarantees well-formedness
- * of the tree.
- ************************************************/
- // L = 'left'
- // R = 'right'
- //
- // the contract is that they can be used as object properties
- // and (-L) === R, and (-R) === L.
- var L = -1;
- var R = 1;
- function prayDirection(dir) {
- pray('a direction was passed', dir === L || dir === R);
- }
- /**
- * Tiny extension of jQuery adding directionalized DOM manipulation methods.
- *
- * Funny how Pjs v3 almost just works with `jQuery.fn.init`.
- *
- * jQuery features that don't work on $:
- * - jQuery.*, like jQuery.ajax, obviously (Pjs doesn't and shouldn't
- * copy constructor properties)
- *
- * - jQuery(function), the shortcut for `jQuery(document).ready(function)`,
- * because `jQuery.fn.init` is idiosyncratic and Pjs doing, essentially,
- * `jQuery.fn.init.apply(this, arguments)` isn't quite right, you need:
- *
- * _.init = function(s, c) { jQuery.fn.init.call(this, s, c, $(document)); };
- *
- * if you actually give a shit (really, don't bother),
- * see https://github.com/jquery/jquery/blob/1.7.2/src/core.js#L889
- *
- * - jQuery(selector), because jQuery translates that to
- * `jQuery(document).find(selector)`, but Pjs doesn't (should it?) let
- * you override the result of a constructor call
- * + note that because of the jQuery(document) shortcut-ness, there's also
- * the 3rd-argument-needs-to-be-`$(document)` thing above, but the fix
- * for that (as can be seen above) is really easy. This problem requires
- * a way more intrusive fix
- *
- * And that's it! Everything else just magically works because jQuery internally
- * uses `this.constructor()` everywhere (hence calling `$`), but never ever does
- * `this.constructor.find` or anything like that, always doing `jQuery.find`.
- */
- var $ = P(jQuery, function(_) {
- _.insDirOf = function(dir, el) {
- return dir === L ?
- this.insertBefore(el.first()) : this.insertAfter(el.last());
- };
- _.insAtDirEnd = function(dir, el) {
- return dir === L ? this.prependTo(el) : this.appendTo(el);
- };
- });
- var Point = P(function(_) {
- _.parent = 0;
- _[L] = 0;
- _[R] = 0;
- _.init = function(parent, leftward, rightward) {
- this.parent = parent;
- this[L] = leftward;
- this[R] = rightward;
- };
- this.copy = function(pt) {
- return Point(pt.parent, pt[L], pt[R]);
- };
- });
- /**
- * MathQuill virtual-DOM tree-node abstract base class
- */
- var Node = P(function(_) {
- _[L] = 0;
- _[R] = 0
- _.parent = 0;
- var id = 0;
- function uniqueNodeId() { return id += 1; }
- this.byId = {};
- _.init = function() {
- this.id = uniqueNodeId();
- Node.byId[this.id] = this;
- this.ends = {};
- this.ends[L] = 0;
- this.ends[R] = 0;
- };
- _.dispose = function() { delete Node.byId[this.id]; };
- _.toString = function() { return '{{ MathQuill Node #'+this.id+' }}'; };
- _.jQ = $();
- _.jQadd = function(jQ) { return this.jQ = this.jQ.add(jQ); };
- _.jQize = function(jQ) {
- // jQuery-ifies this.html() and links up the .jQ of all corresponding Nodes
- var jQ = $(jQ || this.html());
- function jQadd(el) {
- if (el.getAttribute) {
- var cmdId = el.getAttribute('mathquill-command-id');
- var blockId = el.getAttribute('mathquill-block-id');
- if (cmdId) Node.byId[cmdId].jQadd(el);
- if (blockId) Node.byId[blockId].jQadd(el);
- }
- for (el = el.firstChild; el; el = el.nextSibling) {
- jQadd(el);
- }
- }
- for (var i = 0; i < jQ.length; i += 1) jQadd(jQ[i]);
- return jQ;
- };
- _.createDir = function(dir, cursor) {
- prayDirection(dir);
- var node = this;
- node.jQize();
- node.jQ.insDirOf(dir, cursor.jQ);
- cursor[dir] = node.adopt(cursor.parent, cursor[L], cursor[R]);
- return node;
- };
- _.createLeftOf = function(el) { return this.createDir(L, el); };
- _.selectChildren = function(leftEnd, rightEnd) {
- return Selection(leftEnd, rightEnd);
- };
- _.bubble = iterator(function(yield_) {
- for (var ancestor = this; ancestor; ancestor = ancestor.parent) {
- var result = yield_(ancestor);
- if (result === false) break;
- }
- return this;
- });
- _.postOrder = iterator(function(yield_) {
- (function recurse(descendant) {
- descendant.eachChild(recurse);
- yield_(descendant);
- })(this);
- return this;
- });
- _.isEmpty = function() {
- return this.ends[L] === 0 && this.ends[R] === 0;
- };
- _.children = function() {
- return Fragment(this.ends[L], this.ends[R]);
- };
- _.eachChild = function() {
- var children = this.children();
- children.each.apply(children, arguments);
- return this;
- };
- _.foldChildren = function(fold, fn) {
- return this.children().fold(fold, fn);
- };
- _.withDirAdopt = function(dir, parent, withDir, oppDir) {
- Fragment(this, this).withDirAdopt(dir, parent, withDir, oppDir);
- return this;
- };
- _.adopt = function(parent, leftward, rightward) {
- Fragment(this, this).adopt(parent, leftward, rightward);
- return this;
- };
- _.disown = function() {
- Fragment(this, this).disown();
- return this;
- };
- _.remove = function() {
- this.jQ.remove();
- this.postOrder('dispose');
- return this.disown();
- };
- });
- function prayWellFormed(parent, leftward, rightward) {
- pray('a parent is always present', parent);
- pray('leftward is properly set up', (function() {
- // either it's empty and `rightward` is the left end child (possibly empty)
- if (!leftward) return parent.ends[L] === rightward;
- // or it's there and its [R] and .parent are properly set up
- return leftward[R] === rightward && leftward.parent === parent;
- })());
- pray('rightward is properly set up', (function() {
- // either it's empty and `leftward` is the right end child (possibly empty)
- if (!rightward) return parent.ends[R] === leftward;
- // or it's there and its [L] and .parent are properly set up
- return rightward[L] === leftward && rightward.parent === parent;
- })());
- }
- /**
- * An entity outside the virtual tree with one-way pointers (so it's only a
- * "view" of part of the tree, not an actual node/entity in the tree) that
- * delimits a doubly-linked list of sibling nodes.
- * It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
- * classes: like DocumentFragment, its contents must be sibling nodes
- * (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
- * but like Range, it has only one-way pointers to its contents, its contents
- * have no reference to it and in fact may still be in the visible tree (unlike
- * DocumentFragment, whose contents must be detached from the visible tree
- * and have their 'parent' pointers set to the DocumentFragment).
- */
- var Fragment = P(function(_) {
- _.init = function(withDir, oppDir, dir) {
- if (dir === undefined) dir = L;
- prayDirection(dir);
- pray('no half-empty fragments', !withDir === !oppDir);
- this.ends = {};
- if (!withDir) return;
- pray('withDir is passed to Fragment', withDir instanceof Node);
- pray('oppDir is passed to Fragment', oppDir instanceof Node);
- pray('withDir and oppDir have the same parent',
- withDir.parent === oppDir.parent);
- this.ends[dir] = withDir;
- this.ends[-dir] = oppDir;
- // To build the jquery collection for a fragment, accumulate elements
- // into an array and then call jQ.add once on the result. jQ.add sorts the
- // collection according to document order each time it is called, so
- // building a collection by folding jQ.add directly takes more than
- // quadratic time in the number of elements.
- //
- // https://github.com/jquery/jquery/blob/2.1.4/src/traversing.js#L112
- var accum = this.fold([], function (accum, el) {
- accum.push.apply(accum, el.jQ.get());
- return accum;
- });
- this.jQ = this.jQ.add(accum);
- };
- _.jQ = $();
- // like Cursor::withDirInsertAt(dir, parent, withDir, oppDir)
- _.withDirAdopt = function(dir, parent, withDir, oppDir) {
- return (dir === L ? this.adopt(parent, withDir, oppDir)
- : this.adopt(parent, oppDir, withDir));
- };
- _.adopt = function(parent, leftward, rightward) {
- prayWellFormed(parent, leftward, rightward);
- var self = this;
- self.disowned = false;
- var leftEnd = self.ends[L];
- if (!leftEnd) return this;
- var rightEnd = self.ends[R];
- if (leftward) {
- // NB: this is handled in the ::each() block
- // leftward[R] = leftEnd
- } else {
- parent.ends[L] = leftEnd;
- }
- if (rightward) {
- rightward[L] = rightEnd;
- } else {
- parent.ends[R] = rightEnd;
- }
- self.ends[R][R] = rightward;
- self.each(function(el) {
- el[L] = leftward;
- el.parent = parent;
- if (leftward) leftward[R] = el;
- leftward = el;
- });
- return self;
- };
- _.disown = function() {
- var self = this;
- var leftEnd = self.ends[L];
- // guard for empty and already-disowned fragments
- if (!leftEnd || self.disowned) return self;
- self.disowned = true;
- var rightEnd = self.ends[R]
- var parent = leftEnd.parent;
- prayWellFormed(parent, leftEnd[L], leftEnd);
- prayWellFormed(parent, rightEnd, rightEnd[R]);
- if (leftEnd[L]) {
- leftEnd[L][R] = rightEnd[R];
- } else {
- parent.ends[L] = rightEnd[R];
- }
- if (rightEnd[R]) {
- rightEnd[R][L] = leftEnd[L];
- } else {
- parent.ends[R] = leftEnd[L];
- }
- return self;
- };
- _.remove = function() {
- this.jQ.remove();
- this.each('postOrder', 'dispose');
- return this.disown();
- };
- _.each = iterator(function(yield_) {
- var self = this;
- var el = self.ends[L];
- if (!el) return self;
- for (; el !== self.ends[R][R]; el = el[R]) {
- var result = yield_(el);
- if (result === false) break;
- }
- return self;
- });
- _.fold = function(fold, fn) {
- this.each(function(el) {
- fold = fn.call(this, fold, el);
- });
- return fold;
- };
- });
- /**
- * Registry of LaTeX commands and commands created when typing
- * a single character.
- *
- * (Commands are all subclasses of Node.)
- */
- var LatexCmds = {}, CharCmds = {};
- /********************************************
- * Cursor and Selection "singleton" classes
- *******************************************/
- /* The main thing that manipulates the Math DOM. Makes sure to manipulate the
- HTML DOM to match. */
- /* Sort of singletons, since there should only be one per editable math
- textbox, but any one HTML document can contain many such textboxes, so any one
- JS environment could actually contain many instances. */
- //A fake cursor in the fake textbox that the math is rendered in.
- var Cursor = P(Point, function(_) {
- _.init = function(initParent, options) {
- this.parent = initParent;
- this.options = options;
- var jQ = this.jQ = this._jQ = $('<span class="mq-cursor">​</span>');
- //closured for setInterval
- this.blink = function(){ jQ.toggleClass('mq-blink'); };
- this.upDownCache = {};
- };
- _.show = function() {
- this.jQ = this._jQ.removeClass('mq-blink');
- if ('intervalId' in this) //already was shown, just restart interval
- clearInterval(this.intervalId);
- else { //was hidden and detached, insert this.jQ back into HTML DOM
- if (this[R]) {
- if (this.selection && this.selection.ends[L][L] === this[L])
- this.jQ.insertBefore(this.selection.jQ);
- else
- this.jQ.insertBefore(this[R].jQ.first());
- }
- else
- this.jQ.appendTo(this.parent.jQ);
- this.parent.focus();
- }
- this.intervalId = setInterval(this.blink, 500);
- return this;
- };
- _.hide = function() {
- if ('intervalId' in this)
- clearInterval(this.intervalId);
- delete this.intervalId;
- this.jQ.detach();
- this.jQ = $();
- return this;
- };
- _.withDirInsertAt = function(dir, parent, withDir, oppDir) {
- var oldParent = this.parent;
- this.parent = parent;
- this[dir] = withDir;
- this[-dir] = oppDir;
- // by contract, .blur() is called after all has been said and done
- // and the cursor has actually been moved
- if (oldParent !== parent && oldParent.blur) oldParent.blur();
- };
- _.insDirOf = function(dir, el) {
- prayDirection(dir);
- this.jQ.insDirOf(dir, el.jQ);
- this.withDirInsertAt(dir, el.parent, el[dir], el);
- this.parent.jQ.addClass('mq-hasCursor');
- return this;
- };
- _.insLeftOf = function(el) { return this.insDirOf(L, el); };
- _.insRightOf = function(el) { return this.insDirOf(R, el); };
- _.insAtDirEnd = function(dir, el) {
- prayDirection(dir);
- this.jQ.insAtDirEnd(dir, el.jQ);
- this.withDirInsertAt(dir, el, 0, el.ends[dir]);
- el.focus();
- return this;
- };
- _.insAtLeftEnd = function(el) { return this.insAtDirEnd(L, el); };
- _.insAtRightEnd = function(el) { return this.insAtDirEnd(R, el); };
- /**
- * jump up or down from one block Node to another:
- * - cache the current Point in the node we're jumping from
- * - check if there's a Point in it cached for the node we're jumping to
- * + if so put the cursor there,
- * + if not seek a position in the node that is horizontally closest to
- * the cursor's current position
- */
- _.jumpUpDown = function(from, to) {
- var self = this;
- self.upDownCache[from.id] = Point.copy(self);
- var cached = self.upDownCache[to.id];
- if (cached) {
- cached[R] ? self.insLeftOf(cached[R]) : self.insAtRightEnd(cached.parent);
- }
- else {
- var pageX = self.offset().left;
- to.seek(pageX, self);
- }
- };
- _.offset = function() {
- //in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset()
- //returns all 0's on inline elements with negative margin-right (like
- //the cursor) at the end of their parent, so temporarily remove the
- //negative margin-right when calling jQuery::offset()
- //Opera bug DSK-360043
- //http://bugs.jquery.com/ticket/11523
- //https://github.com/jquery/jquery/pull/717
- var self = this, offset = self.jQ.removeClass('mq-cursor').offset();
- self.jQ.addClass('mq-cursor');
- return offset;
- }
- _.unwrapGramp = function() {
- var gramp = this.parent.parent;
- var greatgramp = gramp.parent;
- var rightward = gramp[R];
- var cursor = this;
- var leftward = gramp[L];
- gramp.disown().eachChild(function(uncle) {
- if (uncle.isEmpty()) return;
- uncle.children()
- .adopt(greatgramp, leftward, rightward)
- .each(function(cousin) {
- cousin.jQ.insertBefore(gramp.jQ.first());
- })
- ;
- leftward = uncle.ends[R];
- });
- if (!this[R]) { //then find something to be rightward to insLeftOf
- if (this[L])
- this[R] = this[L][R];
- else {
- while (!this[R]) {
- this.parent = this.parent[R];
- if (this.parent)
- this[R] = this.parent.ends[L];
- else {
- this[R] = gramp[R];
- this.parent = greatgramp;
- break;
- }
- }
- }
- }
- if (this[R])
- this.insLeftOf(this[R]);
- else
- this.insAtRightEnd(greatgramp);
- gramp.jQ.remove();
- if (gramp[L].siblingDeleted) gramp[L].siblingDeleted(cursor.options, R);
- if (gramp[R].siblingDeleted) gramp[R].siblingDeleted(cursor.options, L);
- };
- _.startSelection = function() {
- var anticursor = this.anticursor = Point.copy(this);
- var ancestors = anticursor.ancestors = {}; // a map from each ancestor of
- // the anticursor, to its child that is also an ancestor; in other words,
- // the anticursor's ancestor chain in reverse order
- for (var ancestor = anticursor; ancestor.parent; ancestor = ancestor.parent) {
- ancestors[ancestor.parent.id] = ancestor;
- }
- };
- _.endSelection = function() {
- delete this.anticursor;
- };
- _.select = function() {
- var anticursor = this.anticursor;
- if (this[L] === anticursor[L] && this.parent === anticursor.parent) return false;
- // Find the lowest common ancestor (`lca`), and the ancestor of the cursor
- // whose parent is the LCA (which'll be an end of the selection fragment).
- for (var ancestor = this; ancestor.parent; ancestor = ancestor.parent) {
- if (ancestor.parent.id in anticursor.ancestors) {
- var lca = ancestor.parent;
- break;
- }
- }
- pray('cursor and anticursor in the same tree', lca);
- // The cursor and the anticursor should be in the same tree, because the
- // mousemove handler attached to the document, unlike the one attached to
- // the root HTML DOM element, doesn't try to get the math tree node of the
- // mousemove target, and Cursor::seek() based solely on coordinates stays
- // within the tree of `this` cursor's root.
- // The other end of the selection fragment, the ancestor of the anticursor
- // whose parent is the LCA.
- var antiAncestor = anticursor.ancestors[lca.id];
- // Now we have two either Nodes or Points, guaranteed to have a common
- // parent and guaranteed that if both are Points, they are not the same,
- // and we have to figure out which is the left end and which the right end
- // of the selection.
- var leftEnd, rightEnd, dir = R;
- // This is an extremely subtle algorithm.
- // As a special case, `ancestor` could be a Point and `antiAncestor` a Node
- // immediately to `ancestor`'s left.
- // In all other cases,
- // - both Nodes
- // - `ancestor` a Point and `antiAncestor` a Node
- // - `ancestor` a Node and `antiAncestor` a Point
- // `antiAncestor[R] === rightward[R]` for some `rightward` that is
- // `ancestor` or to its right, if and only if `antiAncestor` is to
- // the right of `ancestor`.
- if (ancestor[L] !== antiAncestor) {
- for (var rightward = ancestor; rightward; rightward = rightward[R]) {
- if (rightward[R] === antiAncestor[R]) {
- dir = L;
- leftEnd = ancestor;
- rightEnd = antiAncestor;
- break;
- }
- }
- }
- if (dir === R) {
- leftEnd = antiAncestor;
- rightEnd = ancestor;
- }
- // only want to select Nodes up to Points, can't select Points themselves
- if (leftEnd instanceof Point) leftEnd = leftEnd[R];
- if (rightEnd instanceof Point) rightEnd = rightEnd[L];
- this.hide().selection = lca.selectChildren(leftEnd, rightEnd);
- this.insDirOf(dir, this.selection.ends[dir]);
- this.selectionChanged();
- return true;
- };
- _.clearSelection = function() {
- if (this.selection) {
- this.selection.clear();
- delete this.selection;
- this.selectionChanged();
- }
- return this;
- };
- _.deleteSelection = function() {
- if (!this.selection) return;
- this[L] = this.selection.ends[L][L];
- this[R] = this.selection.ends[R][R];
- this.selection.remove();
- this.selectionChanged();
- delete this.selection;
- };
- _.replaceSelection = function() {
- var seln = this.selection;
- if (seln) {
- this[L] = seln.ends[L][L];
- this[R] = seln.ends[R][R];
- delete this.selection;
- }
- return seln;
- };
- });
- var Selection = P(Fragment, function(_, super_) {
- _.init = function() {
- super_.init.apply(this, arguments);
- this.jQ = this.jQ.wrapAll('<span class="mq-selection"></span>').parent();
- //can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it
- };
- _.adopt = function() {
- this.jQ.replaceWith(this.jQ = this.jQ.children());
- return super_.adopt.apply(this, arguments);
- };
- _.clear = function() {
- // using the browser's native .childNodes property so that we
- // don't discard text nodes.
- this.jQ.replaceWith(this.jQ[0].childNodes);
- return this;
- };
- _.join = function(methodName) {
- return this.fold('', function(fold, child) {
- return fold + child[methodName]();
- });
- };
- });
- /*********************************************
- * Controller for a MathQuill instance,
- * on which services are registered with
- *
- * Controller.open(function(_) { ... });
- *
- ********************************************/
- var Controller = P(function(_) {
- _.init = function(root, container, options) {
- this.id = root.id;
- this.data = {};
- this.root = root;
- this.container = container;
- this.options = options;
- root.controller = this;
- this.cursor = root.cursor = Cursor(root, options);
- // TODO: stop depending on root.cursor, and rm it
- };
- _.handle = function(name, dir) {
- var handlers = this.options.handlers;
- if (handlers && handlers.fns[name]) {
- var mq = handlers.APIClasses[this.KIND_OF_MQ](this);
- if (dir === L || dir === R) handlers.fns[name](dir, mq);
- else handlers.fns[name](mq);
- }
- };
- var notifyees = [];
- this.onNotify = function(f) { notifyees.push(f); };
- _.notify = function() {
- for (var i = 0; i < notifyees.length; i += 1) {
- notifyees[i].apply(this.cursor, arguments);
- }
- return this;
- };
- });
- /*********************************************************
- * The publicly exposed MathQuill API.
- ********************************************************/
- var API = {}, Options = P(), optionProcessors = {}, Progenote = P(), EMBEDS = {};
- /**
- * Interface Versioning (#459, #495) to allow us to virtually guarantee
- * backcompat. v0.10.x introduces it, so for now, don't completely break the
- * API for people who don't know about it, just complain with console.warn().
- *
- * The methods are shimmed in outro.js so that MQ.MathField.prototype etc can
- * be accessed.
- */
- function insistOnInterVer() {
- if (window.console) console.warn(
- 'You are using the MathQuill API without specifying an interface version, ' +
- 'which will fail in v1.0.0. You can fix this easily by doing this before ' +
- 'doing anything else:\n' +
- '\n' +
- ' MathQuill = MathQuill.getInterface(1);\n' +
- ' // now MathQuill.MathField() works like it used to\n' +
- '\n' +
- 'See also the "`dev` branch (2014\u20132015) \u2192 v0.10.0 Migration Guide" at\n' +
- ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
- );
- }
- // globally exported API object
- function MathQuill(el) {
- insistOnInterVer();
- return MQ1(el);
- };
- MathQuill.prototype = Progenote.p;
- MathQuill.interfaceVersion = function(v) {
- // shim for #459-era interface versioning (ended with #495)
- if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v;
- insistOnInterVer = function() {
- if (window.console) console.warn(
- 'You called MathQuill.interfaceVersion(1); to specify the interface ' +
- 'version, which will fail in v1.0.0. You can fix this easily by doing ' +
- 'this before doing anything else:\n' +
- '\n' +
- ' MathQuill = MathQuill.getInterface(1);\n' +
- ' // now MathQuill.MathField() works like it used to\n' +
- '\n' +
- 'See also the "`dev` branch (2014\u20132015) \u2192 v0.10.0 Migration Guide" at\n' +
- ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
- );
- };
- insistOnInterVer();
- return MathQuill;
- };
- MathQuill.getInterface = getInterface;
- var MIN = getInterface.MIN = 1, MAX = getInterface.MAX = 2;
- function getInterface(v) {
- if (!(MIN <= v && v <= MAX)) throw 'Only interface versions between ' +
- MIN + ' and ' + MAX + ' supported. You specified: ' + v;
- /**
- * Function that takes an HTML element and, if it's the root HTML element of a
- * static math or math or text field, returns an API object for it (else, null).
- *
- * var mathfield = MQ.MathField(mathFieldSpan);
- * assert(MQ(mathFieldSpan).id === mathfield.id);
- * assert(MQ(mathFieldSpan).id === MQ(mathFieldSpan).id);
- *
- */
- function MQ(el) {
- if (!el || !el.nodeType) return null; // check that `el` is a HTML element, using the
- // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92
- var blockId = $(el).children('.mq-root-block').attr(mqBlockId);
- var ctrlr = blockId && Node.byId[blockId].controller;
- return ctrlr ? APIClasses[ctrlr.KIND_OF_MQ](ctrlr) : null;
- };
- var APIClasses = {};
- MQ.L = L;
- MQ.R = R;
- function config(currentOptions, newOptions) {
- if (newOptions && newOptions.handlers) {
- newOptions.handlers = { fns: newOptions.handlers, APIClasses: APIClasses };
- }
- for (var name in newOptions) if (newOptions.hasOwnProperty(name)) {
- var value = newOptions[name], processor = optionProcessors[name];
- currentOptions[name] = (processor ? processor(value) : value);
- }
- }
- MQ.config = function(opts) { config(Options.p, opts); return this; };
- MQ.registerEmbed = function(name, options) {
- if (!/^[a-z][a-z0-9]*$/i.test(name)) {
- throw 'Embed name must start with letter and be only letters and digits';
- }
- EMBEDS[name] = options;
- };
- var AbstractMathQuill = APIClasses.AbstractMathQuill = P(Progenote, function(_) {
- _.init = function(ctrlr) {
- this.__controller = ctrlr;
- this.__options = ctrlr.options;
- this.id = ctrlr.id;
- this.data = ctrlr.data;
- };
- _.__mathquillify = function(classNames) {
- var ctrlr = this.__controller, root = ctrlr.root, el = ctrlr.container;
- ctrlr.createTextarea();
- var contents = el.addClass(classNames).contents().detach();
- root.jQ =
- $('<span class="mq-root-block"/>').attr(mqBlockId, root.id).appendTo(el);
- this.latex(contents.text());
- this.revert = function() {
- return el.empty().unbind('.mathquill')
- .removeClass('mq-editable-field mq-math-mode mq-text-mode')
- .append(contents);
- };
- };
- _.config = function(opts) { config(this.__options, opts); return this; };
- _.el = function() { return this.__controller.container[0]; };
- _.text = function() { return this.__controller.exportText(); };
- _.latex = function(latex) {
- if (arguments.length > 0) {
- this.__controller.renderLatexMath(latex);
- if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
- return this;
- }
- return this.__controller.exportLatex();
- };
- _.html = function() {
- return this.__controller.root.jQ.html()
- .replace(/ mathquill-(?:command|block)-id="?\d+"?/g, '')
- .replace(/<span class="?mq-cursor( mq-blink)?"?>.?<\/span>/i, '')
- .replace(/ mq-hasCursor|mq-hasCursor ?/, '')
- .replace(/ class=(""|(?= |>))/g, '');
- };
- _.reflow = function() {
- this.__controller.root.postOrder('reflow');
- return this;
- };
- });
- MQ.prototype = AbstractMathQuill.prototype;
- APIClasses.EditableField = P(AbstractMathQuill, function(_, super_) {
- _.__mathquillify = function() {
- super_.__mathquillify.apply(this, arguments);
- this.__controller.editable = true;
- this.__controller.delegateMouseEvents();
- this.__controller.editablesTextareaEvents();
- return this;
- };
- _.focus = function() { this.__controller.textarea.focus(); return this; };
- _.blur = function() { this.__controller.textarea.blur(); return this; };
- _.write = function(latex) {
- this.__controller.writeLatex(latex);
- this.__controller.scrollHoriz();
- if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
- return this;
- };
- _.cmd = function(cmd) {
- var ctrlr = this.__controller.notify(), cursor = ctrlr.cursor;
- if (/^\\[a-z]+$/i.test(cmd)) {
- cmd = cmd.slice(1);
- var klass = LatexCmds[cmd];
- if (klass) {
- cmd = klass(cmd);
- if (cursor.selection) cmd.replaces(cursor.replaceSelection());
- cmd.createLeftOf(cursor.show());
- this.__controller.scrollHoriz();
- }
- else /* TODO: API needs better error reporting */;
- }
- else cursor.parent.write(cursor, cmd);
- if (ctrlr.blurred) cursor.hide().parent.blur();
- return this;
- };
- _.select = function() {
- var ctrlr = this.__controller;
- ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
- while (ctrlr.cursor[L]) ctrlr.selectLeft();
- return this;
- };
- _.clearSelection = function() {
- this.__controller.cursor.clearSelection();
- return this;
- };
- _.moveToDirEnd = function(dir) {
- this.__controller.notify('move').cursor.insAtDirEnd(dir, this.__controller.root);
- return this;
- };
- _.moveToLeftEnd = function() { return this.moveToDirEnd(L); };
- _.moveToRightEnd = function() { return this.moveToDirEnd(R); };
- _.keystroke = function(keys) {
- var keys = keys.replace(/^\s+|\s+$/g, '').split(/\s+/);
- for (var i = 0; i < keys.length; i += 1) {
- this.__controller.keystroke(keys[i], { preventDefault: noop });
- }
- return this;
- };
- _.typedText = function(text) {
- for (var i = 0; i < text.length; i += 1) this.__controller.typedText(text.charAt(i));
- return this;
- };
- _.dropEmbedded = function(pageX, pageY, options) {
- var clientX = pageX - $(window).scrollLeft();
- var clientY = pageY - $(window).scrollTop();
- var el = document.elementFromPoint(clientX, clientY);
- this.__controller.seek($(el), pageX, pageY);
- var cmd = Embed().setOptions(options);
- cmd.createLeftOf(this.__controller.cursor);
- };
- });
- MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; };
- MQ.EditableField.prototype = APIClasses.EditableField.prototype;
- /**
- * Export the API functions that MathQuill-ify an HTML element into API objects
- * of each class. If the element had already been MathQuill-ified but into a
- * different kind (or it's not an HTML element), return null.
- */
- for (var kind in API) (function(kind, defAPIClass) {
- var APIClass = APIClasses[kind] = defAPIClass(APIClasses);
- MQ[kind] = function(el, opts) {
- var mq = MQ(el);
- if (mq instanceof APIClass || !el || !el.nodeType) return mq;
- var ctrlr = Controller(APIClass.RootBlock(), $(el), Options());
- ctrlr.KIND_OF_MQ = kind;
- return APIClass(ctrlr).__mathquillify(opts, v);
- };
- MQ[kind].prototype = APIClass.prototype;
- }(kind, API[kind]));
- return MQ;
- }
- MathQuill.noConflict = function() {
- window.MathQuill = origMathQuill;
- return MathQuill;
- };
- var origMathQuill = window.MathQuill;
- window.MathQuill = MathQuill;
- function RootBlockMixin(_) {
- var names = 'moveOutOf deleteOutOf selectOutOf upOutOf downOutOf'.split(' ');
- for (var i = 0; i < names.length; i += 1) (function(name) {
- _[name] = function(dir) { this.controller.handle(name, dir); };
- }(names[i]));
- _.reflow = function() {
- this.controller.handle('reflow');
- this.controller.handle('edited');
- this.controller.handle('edit');
- };
- }
- var Parser = P(function(_, super_, Parser) {
- // The Parser object is a wrapper for a parser function.
- // Externally, you use one to parse a string by calling
- // var result = SomeParser.parse('Me Me Me! Parse Me!');
- // You should never call the constructor, rather you should
- // construct your Parser from the base parsers and the
- // parser combinator methods.
- function parseError(stream, message) {
- if (stream) {
- stream = "'"+stream+"'";
- }
- else {
- stream = 'EOF';
- }
- throw 'Parse Error: '+message+' at '+stream;
- }
- _.init = function(body) { this._ = body; };
- _.parse = function(stream) {
- return this.skip(eof)._(''+stream, success, parseError);
- function success(stream, result) { return result; }
- };
- // -*- primitive combinators -*- //
- _.or = function(alternative) {
- pray('or is passed a parser', alternative instanceof Parser);
- var self = this;
- return Parser(function(stream, onSuccess, onFailure) {
- return self._(stream, onSuccess, failure);
- function failure(newStream) {
- return alternative._(stream, onSuccess, onFailure);
- }
- });
- };
- _.then = function(next) {
- var self = this;
- return Parser(function(stream, onSuccess, onFailure) {
- return self._(stream, success, onFailure);
- function success(newStream, result) {
- var nextParser = (next instanceof Parser ? next : next(result));
- pray('a parser is returned', nextParser instanceof Parser);
- return nextParser._(newStream, onSuccess, onFailure);
- }
- });
- };
- // -*- optimized iterative combinators -*- //
- _.many = function() {
- var self = this;
- return Parser(function(stream, onSuccess, onFailure) {
- var xs = [];
- while (self._(stream, success, failure));
- return onSuccess(stream, xs);
- function success(newStream, x) {
- stream = newStream;
- xs.push(x);
- return true;
- }
- function failure() {
- return false;
- }
- });
- };
- _.times = function(min, max) {
- if (arguments.length < 2) max = min;
- var self = this;
- return Parser(function(stream, onSuccess, onFailure) {
- var xs = [];
- var result = true;
- var failure;
- for (var i = 0; i < min; i += 1) {
- result = self._(stream, success, firstFailure);
- if (!result) return onFailure(stream, failure);
- }
- for (; i < max && result; i += 1) {
- result = self._(stream, success, secondFailure);
- }
- return onSuccess(stream, xs);
- function success(newStream, x) {
- xs.push(x);
- stream = newStream;
- return true;
- }
- function firstFailure(newStream, msg) {
- failure = msg;
- stream = newStream;
- return false;
- }
- function secondFailure(newStream, msg) {
- return false;
- }
- });
- };
- // -*- higher-level combinators -*- //
- _.result = function(res) { return this.then(succeed(res)); };
- _.atMost = function(n) { return this.times(0, n); };
- _.atLeast = function(n) {
- var self = this;
- return self.times(n).then(function(start) {
- return self.many().map(function(end) {
- return start.concat(end);
- });
- });
- };
- _.map = function(fn) {
- return this.then(function(result) { return succeed(fn(result)); });
- };
- _.skip = function(two) {
- return this.then(function(result) { return two.result(result); });
- };
- // -*- primitive parsers -*- //
- var string = this.string = function(str) {
- var len = str.length;
- var expected = "expected '"+str+"'";
- return Parser(function(stream, onSuccess, onFailure) {
- var head = stream.slice(0, len);
- if (head === str) {
- return onSuccess(stream.slice(len), head);
- }
- else {
- return onFailure(stream, expected);
- }
- });
- };
- var regex = this.regex = function(re) {
- pray('regexp parser is anchored', re.toString().charAt(1) === '^');
- var expected = 'expected '+re;
- return Parser(function(stream, onSuccess, onFailure) {
- var match = re.exec(stream);
- if (match) {
- var result = match[0];
- return onSuccess(stream.slice(result.length), result);
- }
- else {
- return onFailure(stream, expected);
- }
- });
- };
- var succeed = Parser.succeed = function(result) {
- return Parser(function(stream, onSuccess) {
- return onSuccess(stream, result);
- });
- };
- var fail = Parser.fail = function(msg) {
- return Parser(function(stream, _, onFailure) {
- return onFailure(stream, msg);
- });
- };
- var letter = Parser.letter = regex(/^[a-z]/i);
- var letters = Parser.letters = regex(/^[a-z]*/i);
- var digit = Parser.digit = regex(/^[0-9]/);
- var digits = Parser.digits = regex(/^[0-9]*/);
- var whitespace = Parser.whitespace = regex(/^\s+/);
- var optWhitespace = Parser.optWhitespace = regex(/^\s*/);
- var any = Parser.any = Parser(function(stream, onSuccess, onFailure) {
- if (!stream) return onFailure(stream, 'expected any character');
- return onSuccess(stream.slice(1), stream.charAt(0));
- });
- var all = Parser.all = Parser(function(stream, onSuccess, onFailure) {
- return onSuccess('', stream);
- });
- var eof = Parser.eof = Parser(function(stream, onSuccess, onFailure) {
- if (stream) return onFailure(stream, 'expected EOF');
- return onSuccess(stream, stream);
- });
- });
- /*************************************************
- * Sane Keyboard Events Shim
- *
- * An abstraction layer wrapping the textarea in
- * an object with methods to manipulate and listen
- * to events on, that hides all the nasty cross-
- * browser incompatibilities behind a uniform API.
- *
- * Design goal: This is a *HARD* internal
- * abstraction barrier. Cross-browser
- * inconsistencies are not allowed to leak through
- * and be dealt with by event handlers. All future
- * cross-browser issues that arise must be dealt
- * with here, and if necessary, the API updated.
- *
- * Organization:
- * - key values map and stringify()
- * - saneKeyboardEvents()
- * + defer() and flush()
- * + event handler logic
- * + attach event handlers and export methods
- ************************************************/
- var saneKeyboardEvents = (function() {
- // The following [key values][1] map was compiled from the
- // [DOM3 Events appendix section on key codes][2] and
- // [a widely cited report on cross-browser tests of key codes][3],
- // except for 10: 'Enter', which I've empirically observed in Safari on iOS
- // and doesn't appear to conflict with any other known key codes.
- //
- // [1]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#keys-keyvalues
- // [2]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes
- // [3]: http://unixpapa.com/js/key.html
- var KEY_VALUES = {
- 8: 'Backspace',
- 9: 'Tab',
- 10: 'Enter', // for Safari on iOS
- 13: 'Enter',
- 16: 'Shift',
- 17: 'Control',
- 18: 'Alt',
- 20: 'CapsLock',
- 27: 'Esc',
- 32: 'Spacebar',
- 33: 'PageUp',
- 34: 'PageDown',
- 35: 'End',
- 36: 'Home',
- 37: 'Left',
- 38: 'Up',
- 39: 'Right',
- 40: 'Down',
- 45: 'Insert',
- 46: 'Del',
- 144: 'NumLock'
- };
- // To the extent possible, create a normalized string representation
- // of the key combo (i.e., key code and modifier keys).
- function stringify(evt) {
- var which = evt.which || evt.keyCode;
- var keyVal = KEY_VALUES[which];
- var key;
- var modifiers = [];
- if (evt.ctrlKey) modifiers.push('Ctrl');
- if (evt.originalEvent && evt.originalEvent.metaKey) modifiers.push('Meta');
- if (evt.altKey) modifiers.push('Alt');
- if (evt.shiftKey) modifiers.push('Shift');
- key = keyVal || String.fromCharCode(which);
- if (!modifiers.length && !keyVal) return key;
- modifiers.push(key);
- return modifiers.join('-');
- }
- // create a keyboard events shim that calls callbacks at useful times
- // and exports useful public methods
- return function saneKeyboardEvents(el, handlers) {
- var keydown = null;
- var keypress = null;
- var textarea = jQuery(el);
- var target = jQuery(handlers.container || textarea);
- // checkTextareaFor() is called after keypress or paste events to
- // say "Hey, I think something was just typed" or "pasted" (resp.),
- // so that at all subsequent opportune times (next event or timeout),
- // will check for expected typed or pasted text.
- // Need to check repeatedly because #135: in Safari 5.1 (at least),
- // after selecting something and then typing, the textarea is
- // incorrectly reported as selected during the input event (but not
- // subsequently).
- var checkTextarea = noop, timeoutId;
- function checkTextareaFor(checker) {
- checkTextarea = checker;
- clearTimeout(timeoutId);
- timeoutId = setTimeout(checker);
- }
- target.bind('keydown keypress input keyup focusout paste', function(e) { checkTextarea(e); });
- // -*- public methods -*- //
- function select(text) {
- // check textarea at least once/one last time before munging (so
- // no race condition if selection happens after keypress/paste but
- // before checkTextarea), then never again ('cos it's been munged)
- checkTextarea();
- checkTextarea = noop;
- clearTimeout(timeoutId);
- textarea.val(text);
- if (text && textarea[0].select) textarea[0].select();
- shouldBeSelected = !!text;
- }
- var shouldBeSelected = false;
- // -*- helper subroutines -*- //
- // Determine whether there's a selection in the textarea.
- // This will always return false in IE < 9, which don't support
- // HTMLTextareaElement::selection{Start,End}.
- function hasSelection() {
- var dom = textarea[0];
- if (!('selectionStart' in dom)) return false;
- return dom.selectionStart !== dom.selectionEnd;
- }
- function handleKey() {
- handlers.keystroke(stringify(keydown), keydown);
- }
- // -*- event handlers -*- //
- function onKeydown(e) {
- keydown = e;
- keypress = null;
- if (shouldBeSelected) checkTextareaFor(function(e) {
- if (!(e && e.type === 'focusout') && textarea[0].select) {
- textarea[0].select(); // re-select textarea in case it's an unrecognized
- }
- checkTextarea = noop; // key that clears the selection, then never
- clearTimeout(timeoutId); // again, 'cos next thing might be blur
- });
- handleKey();
- }
- function onKeypress(e) {
- // call the key handler for repeated keypresses.
- // This excludes keypresses that happen directly
- // after keydown. In that case, there will be
- // no previous keypress, so we skip it here
- if (keydown && keypress) handleKey();
- keypress = e;
- checkTextareaFor(typedText);
- }
- function typedText() {
- // If there is a selection, the contents of the textarea couldn't
- // possibly have just been typed in.
- // This happens in browsers like Firefox and Opera that fire
- // keypress for keystrokes that are not text entry and leave the
- // selection in the textarea alone, such as Ctrl-C.
- // Note: we assume that browsers that don't support hasSelection()
- // also never fire keypress on keystrokes that are not text entry.
- // This seems reasonably safe because:
- // - all modern browsers including IE 9+ support hasSelection(),
- // making it extremely unlikely any browser besides IE < 9 won't
- // - as far as we know IE < 9 never fires keypress on keystrokes
- // that aren't text entry, which is only as reliable as our
- // tests are comprehensive, but the IE < 9 way to do
- // hasSelection() is poorly documented and is also only as
- // reliable as our tests are comprehensive
- // If anything like #40 or #71 is reported in IE < 9, see
- // b1318e5349160b665003e36d4eedd64101ceacd8
- if (hasSelection()) return;
- var text = textarea.val();
- if (text.length === 1) {
- textarea.val('');
- handlers.typedText(text);
- } // in Firefox, keys that don't type text, just clear seln, fire keypress
- // https://github.com/mathquill/mathquill/issues/293#issuecomment-40997668
- else if (text && textarea[0].select) textarea[0].select(); // re-select if that's why we're here
- }
- function onBlur() { keydown = keypress = null; }
- function onPaste(e) {
- // browsers are dumb.
- //
- // In Linux, middle-click pasting causes onPaste to be called,
- // when the textarea is not necessarily focused. We focus it
- // here to ensure that the pasted text actually ends up in the
- // textarea.
- //
- // It's pretty nifty that by changing focus in this handler,
- // we can change the target of the default action. (This works
- // on keydown too, FWIW).
- //
- // And by nifty, we mean dumb (but useful sometimes).
- textarea.focus();
- checkTextareaFor(pastedText);
- }
- function pastedText() {
- var text = textarea.val();
- textarea.val('');
- if (text) handlers.paste(text);
- }
- // -*- attach event handlers -*- //
- target.bind({
- keydown: onKeydown,
- keypress: onKeypress,
- focusout: onBlur,
- paste: onPaste
- });
- // -*- export public methods -*- //
- return {
- select: select
- };
- };
- }());
- /***********************************************
- * Export math in a human-readable text format
- * As you can see, only half-baked so far.
- **********************************************/
- Controller.open(function(_, super_) {
- _.exportText = function() {
- return this.root.foldChildren('', function(text, child) {
- return text + child.text();
- });
- };
- });
- Controller.open(function(_) {
- _.focusBlurEvents = function() {
- var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor;
- var blurTimeout;
- ctrlr.textarea.focus(function() {
- ctrlr.blurred = false;
- clearTimeout(blurTimeout);
- ctrlr.container.addClass('mq-focused');
- if (!cursor.parent)
- cursor.insAtRightEnd(root);
- if (cursor.selection) {
- cursor.selection.jQ.removeClass('mq-blur');
- ctrlr.selectionChanged(); //re-select textarea contents after tabbing away and back
- }
- else
- cursor.show();
- }).blur(function() {
- ctrlr.blurred = true;
- blurTimeout = setTimeout(function() { // wait for blur on window; if
- root.postOrder('intentionalBlur'); // none, intentional blur: #264
- cursor.clearSelection().endSelection();
- blur();
- });
- $(window).on('blur', windowBlur);
- });
- function windowBlur() { // blur event also fired on window, just switching
- clearTimeout(blurTimeout); // tabs/windows, not intentional blur
- if (cursor.selection) cursor.selection.jQ.addClass('mq-blur');
- blur();
- }
- function blur() { // not directly in the textarea blur handler so as to be
- cursor.hide().parent.blur(); // synchronous with/in the same frame as
- ctrlr.container.removeClass('mq-focused'); // clearing/blurring selection
- $(window).off('blur', windowBlur);
- }
- ctrlr.blurred = true;
- cursor.hide().parent.blur();
- };
- });
- /**
- * TODO: I wanted to move MathBlock::focus and blur here, it would clean
- * up lots of stuff like, TextBlock::focus is set to MathBlock::focus
- * and TextBlock::blur calls MathBlock::blur, when instead they could
- * use inheritance and super_.
- *
- * Problem is, there's lots of calls to .focus()/.blur() on nodes
- * outside Controller::focusBlurEvents(), such as .postOrder('blur') on
- * insertion, which if MathBlock::blur becomes Node::blur, would add the
- * 'blur' CSS class to all Symbol's (because .isEmpty() is true for all
- * of them).
- *
- * I'm not even sure there aren't other troublesome calls to .focus() or
- * .blur(), so this is TODO for now.
- */
- /*****************************************
- * Deals with the browser DOM events from
- * interaction with the typist.
- ****************************************/
- Controller.open(function(_) {
- _.keystroke = function(key, evt) {
- this.cursor.parent.keystroke(key, evt, this);
- };
- });
- Node.open(function(_) {
- _.keystroke = function(key, e, ctrlr) {
- var cursor = ctrlr.cursor;
- switch (key) {
- case 'Ctrl-Shift-Backspace':
- case 'Ctrl-Backspace':
- ctrlr.ctrlDeleteDir(L);
- break;
- case 'Shift-Backspace':
- case 'Backspace':
- ctrlr.backspace();
- break;
- // Tab or Esc -> go one block right if it exists, else escape right.
- case 'Esc':
- case 'Tab':
- ctrlr.escapeDir(R, key, e);
- return;
- // Shift-Tab -> go one block left if it exists, else escape left.
- case 'Shift-Tab':
- case 'Shift-Esc':
- ctrlr.escapeDir(L, key, e);
- return;
- // End -> move to the end of the current block.
- case 'End':
- ctrlr.notify('move').cursor.insAtRightEnd(cursor.parent);
- break;
- // Ctrl-End -> move all the way to the end of the root block.
- case 'Ctrl-End':
- ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
- break;
- // Shift-End -> select to the end of the current block.
- case 'Shift-End':
- while (cursor[R]) {
- ctrlr.selectRight();
- }
- break;
- // Ctrl-Shift-End -> select to the end of the root block.
- case 'Ctrl-Shift-End':
- while (cursor[R] || cursor.parent !== ctrlr.root) {
- ctrlr.selectRight();
- }
- break;
- // Home -> move to the start of the root block or the current block.
- case 'Home':
- ctrlr.notify('move').cursor.insAtLeftEnd(cursor.parent);
- break;
- // Ctrl-Home -> move to the start of the current block.
- case 'Ctrl-Home':
- ctrlr.notify('move').cursor.insAtLeftEnd(ctrlr.root);
- break;
- // Shift-Home -> select to the start of the current block.
- case 'Shift-Home':
- while (cursor[L]) {
- ctrlr.selectLeft();
- }
- break;
- // Ctrl-Shift-Home -> move to the start of the root block.
- case 'Ctrl-Shift-Home':
- while (cursor[L] || cursor.parent !== ctrlr.root) {
- ctrlr.selectLeft();
- }
- break;
- case 'Left': ctrlr.moveLeft(); break;
- case 'Shift-Left': ctrlr.selectLeft(); break;
- case 'Ctrl-Left': break;
- case 'Right': ctrlr.moveRight(); break;
- case 'Shift-Right': ctrlr.selectRight(); break;
- case 'Ctrl-Right': break;
- case 'Up': ctrlr.moveUp(); break;
- case 'Down': ctrlr.moveDown(); break;
- case 'Shift-Up':
- if (cursor[L]) {
- while (cursor[L]) ctrlr.selectLeft();
- } else {
- ctrlr.selectLeft();
- }
- case 'Shift-Down':
- if (cursor[R]) {
- while (cursor[R]) ctrlr.selectRight();
- }
- else {
- ctrlr.selectRight();
- }
- case 'Ctrl-Up': break;
- case 'Ctrl-Down': break;
- case 'Ctrl-Shift-Del':
- case 'Ctrl-Del':
- ctrlr.ctrlDeleteDir(R);
- break;
- case 'Shift-Del':
- case 'Del':
- ctrlr.deleteForward();
- break;
- case 'Meta-A':
- case 'Ctrl-A':
- ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
- while (cursor[L]) ctrlr.selectLeft();
- break;
- default:
- return;
- }
- e.preventDefault();
- ctrlr.scrollHoriz();
- };
- _.moveOutOf = // called by Controller::escapeDir, moveDir
- _.moveTowards = // called by Controller::moveDir
- _.deleteOutOf = // called by Controller::deleteDir
- _.deleteTowards = // called by Controller::deleteDir
- _.unselectInto = // called by Controller::selectDir
- _.selectOutOf = // called by Controller::selectDir
- _.selectTowards = // called by Controller::selectDir
- function() { pray('overridden or never called on this node'); };
- });
- Controller.open(function(_) {
- this.onNotify(function(e) {
- if (e === 'move' || e === 'upDown') this.show().clearSelection();
- });
- _.escapeDir = function(dir, key, e) {
- prayDirection(dir);
- var cursor = this.cursor;
- // only prevent default of Tab if not in the root editable
- if (cursor.parent !== this.root) e.preventDefault();
- // want to be a noop if in the root editable (in fact, Tab has an unrelated
- // default browser action if so)
- if (cursor.parent === this.root) return;
- cursor.parent.moveOutOf(dir, cursor);
- return this.notify('move');
- };
- optionProcessors.leftRightIntoCmdGoes = function(updown) {
- if (updown && updown !== 'up' && updown !== 'down') {
- throw '"up" or "down" required for leftRightIntoCmdGoes option, '
- + 'got "'+updown+'"';
- }
- return updown;
- };
- _.moveDir = function(dir) {
- prayDirection(dir);
- var cursor = this.cursor, updown = cursor.options.leftRightIntoCmdGoes;
- if (cursor.selection) {
- cursor.insDirOf(dir, cursor.selection.ends[dir]);
- }
- else if (cursor[dir]) cursor[dir].moveTowards(dir, cursor, updown);
- else cursor.parent.moveOutOf(dir, cursor, updown);
- return this.notify('move');
- };
- _.moveLeft = function() { return this.moveDir(L); };
- _.moveRight = function() { return this.moveDir(R); };
- /**
- * moveUp and moveDown have almost identical algorithms:
- * - first check left and right, if so insAtLeft/RightEnd of them
- * - else check the parent's 'upOutOf'/'downOutOf' property:
- * + if it's a function, call it with the cursor as the sole argument and
- * use the return value as if it were the value of the property
- * + if it's a Node, jump up or down into it:
- * - if there is a cached Point in the block, insert there
- * - else, seekHoriz within the block to the current x-coordinate (to be
- * as close to directly above/below the current position as possible)
- * + unless it's exactly `true`, stop bubbling
- */
- _.moveUp = function() { return moveUpDown(this, 'up'); };
- _.moveDown = function() { return moveUpDown(this, 'down'); };
- function moveUpDown(self, dir) {
- var cursor = self.notify('upDown').cursor;
- var dirInto = dir+'Into', dirOutOf = dir+'OutOf';
- if (cursor[R][dirInto]) cursor.insAtLeftEnd(cursor[R][dirInto]);
- else if (cursor[L][dirInto]) cursor.insAtRightEnd(cursor[L][dirInto]);
- else {
- cursor.parent.bubble(function(ancestor) {
- var prop = ancestor[dirOutOf];
- if (prop) {
- if (typeof prop === 'function') prop = ancestor[dirOutOf](cursor);
- if (prop instanceof Node) cursor.jumpUpDown(ancestor, prop);
- if (prop !== true) return false;
- }
- });
- }
- return self;
- }
- this.onNotify(function(e) { if (e !== 'upDown') this.upDownCache = {}; });
- this.onNotify(function(e) { if (e === 'edit') this.show().deleteSelection(); });
- _.deleteDir = function(dir) {
- prayDirection(dir);
- var cursor = this.cursor;
- var hadSelection = cursor.selection;
- this.notify('edit'); // deletes selection if present
- if (!hadSelection) {
- if (cursor[dir]) cursor[dir].deleteTowards(dir, cursor);
- else cursor.parent.deleteOutOf(dir, cursor);
- }
- if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
- if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
- cursor.parent.bubble('reflow');
- return this;
- };
- _.ctrlDeleteDir = function(dir) {
- prayDirection(dir);
- var cursor = this.cursor;
- if (!cursor[L] || cursor.selection) return ctrlr.deleteDir();
- this.notify('edit');
- Fragment(cursor.parent.ends[L], cursor[L]).remove();
- cursor.insAtDirEnd(L, cursor.parent);
- if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
- if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
- cursor.parent.bubble('reflow');
- return this;
- };
- _.backspace = function() { return this.deleteDir(L); };
- _.deleteForward = function() { return this.deleteDir(R); };
- this.onNotify(function(e) { if (e !== 'select') this.endSelection(); });
- _.selectDir = function(dir) {
- var cursor = this.notify('select').cursor, seln = cursor.selection;
- prayDirection(dir);
- if (!cursor.anticursor) cursor.startSelection();
- var node = cursor[dir];
- if (node) {
- // "if node we're selecting towards is inside selection (hence retracting)
- // and is on the *far side* of the selection (hence is only node selected)
- // and the anticursor is *inside* that node, not just on the other side"
- if (seln && seln.ends[dir] === node && cursor.anticursor[-dir] !== node) {
- node.unselectInto(dir, cursor);
- }
- else node.selectTowards(dir, cursor);
- }
- else cursor.parent.selectOutOf(dir, cursor);
- cursor.clearSelection();
- cursor.select() || cursor.show();
- };
- _.selectLeft = function() { return this.selectDir(L); };
- _.selectRight = function() { return this.selectDir(R); };
- });
- // Parser MathCommand
- var latexMathParser = (function() {
- function commandToBlock(cmd) {
- var block = MathBlock();
- cmd.adopt(block, 0, 0);
- return block;
- }
- function joinBlocks(blocks) {
- var firstBlock = blocks[0] || MathBlock();
- for (var i = 1; i < blocks.length; i += 1) {
- blocks[i].children().adopt(firstBlock, firstBlock.ends[R], 0);
- }
- return firstBlock;
- }
- var string = Parser.string;
- var regex = Parser.regex;
- var letter = Parser.letter;
- var any = Parser.any;
- var optWhitespace = Parser.optWhitespace;
- var succeed = Parser.succeed;
- var fail = Parser.fail;
- // Parsers yielding either MathCommands, or Fragments of MathCommands
- // (either way, something that can be adopted by a MathBlock)
- var variable = letter.map(function(c) { return Letter(c); });
- var symbol = regex(/^[^${}\\_^]/).map(function(c) { return VanillaSymbol(c); });
- var controlSequence =
- regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write
- .or(string('\\').then(
- regex(/^[a-z]+/i)
- .or(regex(/^\s+/).result(' '))
- .or(any)
- )).then(function(ctrlSeq) {
- var cmdKlass = LatexCmds[ctrlSeq];
- if (cmdKlass) {
- return cmdKlass(ctrlSeq).parser();
- }
- else {
- return fail('unknown command: \\'+ctrlSeq);
- }
- })
- ;
- var command =
- controlSequence
- .or(variable)
- .or(symbol)
- ;
- // Parsers yielding MathBlocks
- var mathGroup = string('{').then(function() { return mathSequence; }).skip(string('}'));
- var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock)));
- var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace);
- var optMathBlock =
- string('[').then(
- mathBlock.then(function(block) {
- return block.join('latex') !== ']' ? succeed(block) : fail();
- })
- .many().map(joinBlocks).skip(optWhitespace)
- ).skip(string(']'))
- ;
- var latexMath = mathSequence;
- latexMath.block = mathBlock;
- latexMath.optBlock = optMathBlock;
- return latexMath;
- })();
- Controller.open(function(_, super_) {
- _.exportLatex = function() {
- return this.root.latex().replace(/(\\[a-z]+) (?![a-z])/ig,'$1');
- };
- _.writeLatex = function(latex) {
- var cursor = this.notify('edit').cursor;
- var all = Parser.all;
- var eof = Parser.eof;
- var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
- if (block && !block.isEmpty()) {
- block.children().adopt(cursor.parent, cursor[L], cursor[R]);
- var jQ = block.jQize();
- jQ.insertBefore(cursor.jQ);
- cursor[L] = block.ends[R];
- block.finalizeInsert(cursor.options, cursor);
- if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
- if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
- cursor.parent.bubble('reflow');
- }
- return this;
- };
- _.renderLatexMath = function(latex) {
- var root = this.root, cursor = this.cursor;
- var all = Parser.all;
- var eof = Parser.eof;
- var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
- root.eachChild('postOrder', 'dispose');
- root.ends[L] = root.ends[R] = 0;
- if (block) {
- block.children().adopt(root, 0, 0);
- }
- var jQ = root.jQ;
- if (block) {
- var html = block.join('html');
- jQ.html(html);
- root.jQize(jQ.children());
- root.finalizeInsert(cursor.options);
- }
- else {
- jQ.empty();
- }
- delete cursor.selection;
- cursor.insAtRightEnd(root);
- };
- _.renderLatexText = function(latex) {
- var root = this.root, cursor = this.cursor;
- root.jQ.children().slice(1).remove();
- root.eachChild('postOrder', 'dispose');
- root.ends[L] = root.ends[R] = 0;
- delete cursor.selection;
- cursor.show().insAtRightEnd(root);
- var regex = Parser.regex;
- var string = Parser.string;
- var eof = Parser.eof;
- var all = Parser.all;
- // Parser RootMathCommand
- var mathMode = string('$').then(latexMathParser)
- // because TeX is insane, math mode doesn't necessarily
- // have to end. So we allow for the case that math mode
- // continues to the end of the stream.
- .skip(string('$').or(eof))
- .map(function(block) {
- // HACK FIXME: this shouldn't have to have access to cursor
- var rootMathCommand = RootMathCommand(cursor);
- rootMathCommand.createBlocks();
- var rootMathBlock = rootMathCommand.ends[L];
- block.children().adopt(rootMathBlock, 0, 0);
- return rootMathCommand;
- })
- ;
- var escapedDollar = string('\\$').result('$');
- var textChar = escapedDollar.or(regex(/^[^$]/)).map(VanillaSymbol);
- var latexText = mathMode.or(textChar).many();
- var commands = latexText.skip(eof).or(all.result(false)).parse(latex);
- if (commands) {
- for (var i = 0; i < commands.length; i += 1) {
- commands[i].adopt(root, root.ends[R], 0);
- }
- root.jQize().appendTo(root.jQ);
- root.finalizeInsert(cursor.options);
- }
- };
- });
- /********************************************************
- * Deals with mouse events for clicking, drag-to-select
- *******************************************************/
- Controller.open(function(_) {
- _.delegateMouseEvents = function() {
- var ultimateRootjQ = this.root.jQ;
- //drag-to-select event handling
- this.container.bind('mousedown.mathquill', function(e) {
- var rootjQ = $(e.target).closest('.mq-root-block');
- var root = Node.byId[rootjQ.attr(mqBlockId) || ultimateRootjQ.attr(mqBlockId)];
- var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink;
- var textareaSpan = ctrlr.textareaSpan, textarea = ctrlr.textarea;
- var target;
- function mousemove(e) { target = $(e.target); }
- function docmousemove(e) {
- if (!cursor.anticursor) cursor.startSelection();
- ctrlr.seek(target, e.pageX, e.pageY).cursor.select();
- target = undefined;
- }
- // outside rootjQ, the MathQuill node corresponding to the target (if any)
- // won't be inside this root, so don't mislead Controller::seek with it
- function mouseup(e) {
- cursor.blink = blink;
- if (!cursor.selection) {
- if (ctrlr.editable) {
- cursor.show();
- }
- else {
- textareaSpan.detach();
- }
- }
- // delete the mouse handlers now that we're not dragging anymore
- rootjQ.unbind('mousemove', mousemove);
- $(e.target.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup', mouseup);
- }
- if (ctrlr.blurred) {
- if (!ctrlr.editable) rootjQ.prepend(textareaSpan);
- textarea.focus();
- }
- e.preventDefault(); // doesn't work in IE\u22648, but it's a one-line fix:
- e.target.unselectable = true; // http://jsbin.com/yagekiji/1
- cursor.blink = noop;
- ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection();
- rootjQ.mousemove(mousemove);
- $(e.target.ownerDocument).mousemove(docmousemove).mouseup(mouseup);
- // listen on document not just body to not only hear about mousemove and
- // mouseup on page outside field, but even outside page, except iframes: https://github.com/mathquill/mathquill/commit/8c50028afcffcace655d8ae2049f6e02482346c5#commitcomment-6175800
- });
- }
- });
- Controller.open(function(_) {
- _.seek = function(target, pageX, pageY) {
- var cursor = this.notify('select').cursor;
- if (target) {
- var nodeId = target.attr(mqBlockId) || target.attr(mqCmdId);
- if (!nodeId) {
- var targetParent = target.parent();
- nodeId = targetParent.attr(mqBlockId) || targetParent.attr(mqCmdId);
- }
- }
- var node = nodeId ? Node.byId[nodeId] : this.root;
- pray('nodeId is the id of some Node that exists', node);
- // don't clear selection until after getting node from target, in case
- // target was selection span, otherwise target will have no parent and will
- // seek from root, which is less accurate (e.g. fraction)
- cursor.clearSelection().show();
- node.seek(pageX, cursor);
- this.scrollHoriz(); // before .selectFrom when mouse-selecting, so
- // always hits no-selection case in scrollHoriz and scrolls slower
- return this;
- };
- });
- /***********************************************
- * Horizontal panning for editable fields that
- * overflow their width
- **********************************************/
- Controller.open(function(_) {
- _.scrollHoriz = function() {
- var cursor = this.cursor, seln = cursor.selection;
- var rootRect = this.root.jQ[0].getBoundingClientRect();
- if (!seln) {
- var x = cursor.jQ[0].getBoundingClientRect().left;
- if (x > rootRect.right - 20) var scrollBy = x - (rootRect.right - 20);
- else if (x < rootRect.left + 20) var scrollBy = x - (rootRect.left + 20);
- else return;
- }
- else {
- var rect = seln.jQ[0].getBoundingClientRect();
- var overLeft = rect.left - (rootRect.left + 20);
- var overRight = rect.right - (rootRect.right - 20);
- if (seln.ends[L] === cursor[R]) {
- if (overLeft < 0) var scrollBy = overLeft;
- else if (overRight > 0) {
- if (rect.left - overRight < rootRect.left + 20) var scrollBy = overLeft;
- else var scrollBy = overRight;
- }
- else return;
- }
- else {
- if (overRight > 0) var scrollBy = overRight;
- else if (overLeft < 0) {
- if (rect.right - overLeft > rootRect.right - 20) var scrollBy = overRight;
- else var scrollBy = overLeft;
- }
- else return;
- }
- }
- this.root.jQ.stop().animate({ scrollLeft: '+=' + scrollBy}, 100);
- };
- });
- /*********************************************
- * Manage the MathQuill instance's textarea
- * (as owned by the Controller)
- ********************************************/
- Controller.open(function(_) {
- Options.p.substituteTextarea = function() {
- return $('<textarea autocapitalize=off autocomplete=off autocorrect=off ' +
- 'spellcheck=false x-palm-disable-ste-all=true />')[0];
- };
- _.createTextarea = function() {
- var textareaSpan = this.textareaSpan = $('<span class="mq-textarea"></span>'),
- textarea = this.options.substituteTextarea();
- if (!textarea.nodeType) {
- throw 'substituteTextarea() must return a DOM element, got ' + textarea;
- }
- textarea = this.textarea = $(textarea).appendTo(textareaSpan);
- var ctrlr = this;
- ctrlr.cursor.selectionChanged = function() { ctrlr.selectionChanged(); };
- ctrlr.container.bind('copy', function() { ctrlr.setTextareaSelection(); });
- };
- _.selectionChanged = function() {
- var ctrlr = this;
- forceIERedraw(ctrlr.container[0]);
- // throttle calls to setTextareaSelection(), because setting textarea.value
- // and/or calling textarea.select() can have anomalously bad performance:
- // https://github.com/mathquill/mathquill/issues/43#issuecomment-1399080
- if (ctrlr.textareaSelectionTimeout === undefined) {
- ctrlr.textareaSelectionTimeout = setTimeout(function() {
- ctrlr.setTextareaSelection();
- });
- }
- };
- _.setTextareaSelection = function() {
- this.textareaSelectionTimeout = undefined;
- var latex = '';
- if (this.cursor.selection) {
- latex = this.cursor.selection.join('latex');
- if (this.options.statelessClipboard) {
- // FIXME: like paste, only this works for math fields; should ask parent
- latex = '$' + latex + '$';
- }
- }
- this.selectFn(latex);
- };
- _.staticMathTextareaEvents = function() {
- var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
- textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
- this.container.prepend('<span class="mq-selectable">$'+ctrlr.exportLatex()+'$</span>');
- ctrlr.blurred = true;
- textarea.bind('cut paste', false)
- .focus(function() { ctrlr.blurred = false; }).blur(function() {
- if (cursor.selection) cursor.selection.clear();
- setTimeout(detach); //detaching during blur explodes in WebKit
- });
- function detach() {
- textareaSpan.detach();
- ctrlr.blurred = true;
- }
- ctrlr.selectFn = function(text) {
- textarea.val(text);
- if (text) textarea.select();
- };
- };
- _.editablesTextareaEvents = function() {
- var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
- textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
- var keyboardEventsShim = saneKeyboardEvents(textarea, this);
- this.selectFn = function(text) { keyboardEventsShim.select(text); };
- this.container.prepend(textareaSpan)
- .on('cut', function(e) {
- if (cursor.selection) {
- setTimeout(function() {
- ctrlr.notify('edit'); // deletes selection if present
- cursor.parent.bubble('reflow');
- });
- }
- });
- this.focusBlurEvents();
- };
- _.typedText = function(ch) {
- if (ch === '\n') return this.handle('enter');
- var cursor = this.notify().cursor;
- cursor.parent.write(cursor, ch);
- this.scrollHoriz();
- };
- _.paste = function(text) {
- // TODO: document `statelessClipboard` config option in README, after
- // making it work like it should, that is, in both text and math mode
- // (currently only works in math fields, so worse than pointless, it
- // only gets in the way by \text{}-ifying pasted stuff and $-ifying
- // cut/copied LaTeX)
- if (this.options.statelessClipboard) {
- if (text.slice(0,1) === '$' && text.slice(-1) === '$') {
- text = text.slice(1, -1);
- }
- else {
- text = '\\text{'+text+'}';
- }
- }
- // FIXME: this always inserts math or a TextBlock, even in a RootTextBlock
- this.writeLatex(text).cursor.show();
- };
- });
- /*************************************************
- * Abstract classes of math blocks and commands.
- ************************************************/
- /**
- * Math tree node base class.
- * Some math-tree-specific extensions to Node.
- * Both MathBlock's and MathCommand's descend from it.
- */
- var MathElement = P(Node, function(_, super_) {
- _.finalizeInsert = function(options, cursor) { // `cursor` param is only for
- // SupSub::contactWeld, and is deliberately only passed in by writeLatex,
- // see ea7307eb4fac77c149a11ffdf9a831df85247693
- var self = this;
- self.postOrder('finalizeTree', options);
- self.postOrder('contactWeld', cursor);
- // note: this order is important.
- // empty elements need the empty box provided by blur to
- // be present in order for their dimensions to be measured
- // correctly by 'reflow' handlers.
- self.postOrder('blur');
- self.postOrder('reflow');
- if (self[R].siblingCreated) self[R].siblingCreated(options, L);
- if (self[L].siblingCreated) self[L].siblingCreated(options, R);
- self.bubble('reflow');
- };
- });
- /**
- * Commands and operators, like subscripts, exponents, or fractions.
- * Descendant commands are organized into blocks.
- */
- var MathCommand = P(MathElement, function(_, super_) {
- _.init = function(ctrlSeq, htmlTemplate, textTemplate) {
- var cmd = this;
- super_.init.call(cmd);
- if (!cmd.ctrlSeq) cmd.ctrlSeq = ctrlSeq;
- if (htmlTemplate) cmd.htmlTemplate = htmlTemplate;
- if (textTemplate) cmd.textTemplate = textTemplate;
- };
- // obvious methods
- _.replaces = function(replacedFragment) {
- replacedFragment.disown();
- this.replacedFragment = replacedFragment;
- };
- _.isEmpty = function() {
- return this.foldChildren(true, function(isEmpty, child) {
- return isEmpty && child.isEmpty();
- });
- };
- _.parser = function() {
- var block = latexMathParser.block;
- var self = this;
- return block.times(self.numBlocks()).map(function(blocks) {
- self.blocks = blocks;
- for (var i = 0; i < blocks.length; i += 1) {
- blocks[i].adopt(self, self.ends[R], 0);
- }
- return self;
- });
- };
- // createLeftOf(cursor) and the methods it calls
- _.createLeftOf = function(cursor) {
- var cmd = this;
- var replacedFragment = cmd.replacedFragment;
- cmd.createBlocks();
- super_.createLeftOf.call(cmd, cursor);
- if (replacedFragment) {
- replacedFragment.adopt(cmd.ends[L], 0, 0);
- replacedFragment.jQ.appendTo(cmd.ends[L].jQ);
- }
- cmd.finalizeInsert(cursor.options);
- cmd.placeCursor(cursor);
- };
- _.createBlocks = function() {
- var cmd = this,
- numBlocks = cmd.numBlocks(),
- blocks = cmd.blocks = Array(numBlocks);
- for (var i = 0; i < numBlocks; i += 1) {
- var newBlock = blocks[i] = MathBlock();
- newBlock.adopt(cmd, cmd.ends[R], 0);
- }
- };
- _.placeCursor = function(cursor) {
- //insert the cursor at the right end of the first empty child, searching
- //left-to-right, or if none empty, the right end child
- cursor.insAtRightEnd(this.foldChildren(this.ends[L], function(leftward, child) {
- return leftward.isEmpty() ? leftward : child;
- }));
- };
- // editability methods: called by the cursor for editing, cursor movements,
- // and selection of the MathQuill tree, these all take in a direction and
- // the cursor
- _.moveTowards = function(dir, cursor, updown) {
- var updownInto = updown && this[updown+'Into'];
- cursor.insAtDirEnd(-dir, updownInto || this.ends[-dir]);
- };
- _.deleteTowards = function(dir, cursor) {
- if (this.isEmpty()) cursor[dir] = this.remove()[dir];
- else this.moveTowards(dir, cursor, null);
- };
- _.selectTowards = function(dir, cursor) {
- cursor[-dir] = this;
- cursor[dir] = this[dir];
- };
- _.selectChildren = function() {
- return Selection(this, this);
- };
- _.unselectInto = function(dir, cursor) {
- cursor.insAtDirEnd(-dir, cursor.anticursor.ancestors[this.id]);
- };
- _.seek = function(pageX, cursor) {
- function getBounds(node) {
- var bounds = {}
- bounds[L] = node.jQ.offset().left;
- bounds[R] = bounds[L] + node.jQ.outerWidth();
- return bounds;
- }
- var cmd = this;
- var cmdBounds = getBounds(cmd);
- if (pageX < cmdBounds[L]) return cursor.insLeftOf(cmd);
- if (pageX > cmdBounds[R]) return cursor.insRightOf(cmd);
- var leftLeftBound = cmdBounds[L];
- cmd.eachChild(function(block) {
- var blockBounds = getBounds(block);
- if (pageX < blockBounds[L]) {
- // closer to this block's left bound, or the bound left of that?
- if (pageX - leftLeftBound < blockBounds[L] - pageX) {
- if (block[L]) cursor.insAtRightEnd(block[L]);
- else cursor.insLeftOf(cmd);
- }
- else cursor.insAtLeftEnd(block);
- return false;
- }
- else if (pageX > blockBounds[R]) {
- if (block[R]) leftLeftBound = blockBounds[R]; // continue to next block
- else { // last (rightmost) block
- // closer to this block's right bound, or the cmd's right bound?
- if (cmdBounds[R] - pageX < pageX - blockBounds[R]) {
- cursor.insRightOf(cmd);
- }
- else cursor.insAtRightEnd(block);
- }
- }
- else {
- block.seek(pageX, cursor);
- return false;
- }
- });
- }
- // methods involved in creating and cross-linking with HTML DOM nodes
- /*
- They all expect an .htmlTemplate like
- '<span>&0</span>'
- or
- '<span><span>&0</span><span>&1</span></span>'
- See html.test.js for more examples.
- Requirements:
- - For each block of the command, there must be exactly one "block content
- marker" of the form '&<number>' where <number> is the 0-based index of the
- block. (Like the LaTeX \newcommand syntax, but with a 0-based rather than
- 1-based index, because JavaScript because C because Dijkstra.)
- - The block content marker must be the sole contents of the containing
- element, there can't even be surrounding whitespace, or else we can't
- guarantee sticking to within the bounds of the block content marker when
- mucking with the HTML DOM.
- - The HTML not only must be well-formed HTML (of course), but also must
- conform to the XHTML requirements on tags, specifically all tags must
- either be self-closing (like '<br/>') or come in matching pairs.
- Close tags are never optional.
- Note that &<number> isn't well-formed HTML; if you wanted a literal '&123',
- your HTML template would have to have '&123'.
- */
- _.numBlocks = function() {
- var matches = this.htmlTemplate.match(/&\d+/g);
- return matches ? matches.length : 0;
- };
- _.html = function() {
- // Render the entire math subtree rooted at this command, as HTML.
- // Expects .createBlocks() to have been called already, since it uses the
- // .blocks array of child blocks.
- //
- // See html.test.js for example templates and intended outputs.
- //
- // Given an .htmlTemplate as described above,
- // - insert the mathquill-command-id attribute into all top-level tags,
- // which will be used to set this.jQ in .jQize().
- // This is straightforward:
- // * tokenize into tags and non-tags
- // * loop through top-level tokens:
- // * add #cmdId attribute macro to top-level self-closing tags
- // * else add #cmdId attribute macro to top-level open tags
- // * skip the matching top-level close tag and all tag pairs
- // in between
- // - for each block content marker,
- // + replace it with the contents of the corresponding block,
- // rendered as HTML
- // + insert the mathquill-block-id attribute into the containing tag
- // This is even easier, a quick regex replace, since block tags cannot
- // contain anything besides the block content marker.
- //
- // Two notes:
- // - The outermost loop through top-level tokens should never encounter any
- // top-level close tags, because we should have first encountered a
- // matching top-level open tag, all inner tags should have appeared in
- // matching pairs and been skipped, and then we should have skipped the
- // close tag in question.
- // - All open tags should have matching close tags, which means our inner
- // loop should always encounter a close tag and drop nesting to 0. If
- // a close tag is missing, the loop will continue until i >= tokens.length
- // and token becomes undefined. This will not infinite loop, even in
- // production without pray(), because it will then TypeError on .slice().
- var cmd = this;
- var blocks = cmd.blocks;
- var cmdId = ' mathquill-command-id=' + cmd.id;
- var tokens = cmd.htmlTemplate.match(/<[^<>]+>|[^<>]+/g);
- pray('no unmatched angle brackets', tokens.join('') === this.htmlTemplate);
- // add cmdId to all top-level tags
- for (var i = 0, token = tokens[0]; token; i += 1, token = tokens[i]) {
- // top-level self-closing tags
- if (token.slice(-2) === '/>') {
- tokens[i] = token.slice(0,-2) + cmdId + '/>';
- }
- // top-level open tags
- else if (token.charAt(0) === '<') {
- pray('not an unmatched top-level close tag', token.charAt(1) !== '/');
- tokens[i] = token.slice(0,-1) + cmdId + '>';
- // skip matching top-level close tag and all tag pairs in between
- var nesting = 1;
- do {
- i += 1, token = tokens[i];
- pray('no missing close tags', token);
- // close tags
- if (token.slice(0,2) === '</') {
- nesting -= 1;
- }
- // non-self-closing open tags
- else if (token.charAt(0) === '<' && token.slice(-2) !== '/>') {
- nesting += 1;
- }
- } while (nesting > 0);
- }
- }
- return tokens.join('').replace(/>&(\d+)/g, function($0, $1) {
- return ' mathquill-block-id=' + blocks[$1].id + '>' + blocks[$1].join('html');
- });
- };
- // methods to export a string representation of the math tree
- _.latex = function() {
- return this.foldChildren(this.ctrlSeq, function(latex, child) {
- return latex + '{' + (child.latex() || ' ') + '}';
- });
- };
- _.textTemplate = [''];
- _.text = function() {
- var cmd = this, i = 0;
- return cmd.foldChildren(cmd.textTemplate[i], function(text, child) {
- i += 1;
- var child_text = child.text();
- if (text && cmd.textTemplate[i] === '('
- && child_text[0] === '(' && child_text.slice(-1) === ')')
- return text + child_text.slice(1, -1) + cmd.textTemplate[i];
- return text + child.text() + (cmd.textTemplate[i] || '');
- });
- };
- });
- /**
- * Lightweight command without blocks or children.
- */
- var Symbol = P(MathCommand, function(_, super_) {
- _.init = function(ctrlSeq, html, text) {
- if (!text) text = ctrlSeq && ctrlSeq.length > 1 ? ctrlSeq.slice(1) : ctrlSeq;
- super_.init.call(this, ctrlSeq, html, [ text ]);
- };
- _.parser = function() { return Parser.succeed(this); };
- _.numBlocks = function() { return 0; };
- _.replaces = function(replacedFragment) {
- replacedFragment.remove();
- };
- _.createBlocks = noop;
- _.moveTowards = function(dir, cursor) {
- cursor.jQ.insDirOf(dir, this.jQ);
- cursor[-dir] = this;
- cursor[dir] = this[dir];
- };
- _.deleteTowards = function(dir, cursor) {
- cursor[dir] = this.remove()[dir];
- };
- _.seek = function(pageX, cursor) {
- // insert at whichever side the click was closer to
- if (pageX - this.jQ.offset().left < this.jQ.outerWidth()/2)
- cursor.insLeftOf(this);
- else
- cursor.insRightOf(this);
- };
- _.latex = function(){ return this.ctrlSeq; };
- _.text = function(){ return this.textTemplate; };
- _.placeCursor = noop;
- _.isEmpty = function(){ return true; };
- });
- var VanillaSymbol = P(Symbol, function(_, super_) {
- _.init = function(ch, html) {
- super_.init.call(this, ch, '<span>'+(html || ch)+'</span>');
- };
- });
- var BinaryOperator = P(Symbol, function(_, super_) {
- _.init = function(ctrlSeq, html, text) {
- super_.init.call(this,
- ctrlSeq, '<span class="mq-binary-operator">'+html+'</span>', text
- );
- };
- });
- /**
- * Children and parent of MathCommand's. Basically partitions all the
- * symbols and operators that descend (in the Math DOM tree) from
- * ancestor operators.
- */
- var MathBlock = P(MathElement, function(_, super_) {
- _.join = function(methodName) {
- return this.foldChildren('', function(fold, child) {
- return fold + child[methodName]();
- });
- };
- _.html = function() { return this.join('html'); };
- _.latex = function() { return this.join('latex'); };
- _.text = function() {
- return (this.ends[L] === this.ends[R] && this.ends[L] !== 0) ?
- this.ends[L].text() :
- this.join('text')
- ;
- };
- _.keystroke = function(key, e, ctrlr) {
- if (ctrlr.options.spaceBehavesLikeTab
- && (key === 'Spacebar' || key === 'Shift-Spacebar')) {
- e.preventDefault();
- ctrlr.escapeDir(key === 'Shift-Spacebar' ? L : R, key, e);
- return;
- }
- return super_.keystroke.apply(this, arguments);
- };
- // editability methods: called by the cursor for editing, cursor movements,
- // and selection of the MathQuill tree, these all take in a direction and
- // the cursor
- _.moveOutOf = function(dir, cursor, updown) {
- var updownInto = updown && this.parent[updown+'Into'];
- if (!updownInto && this[dir]) cursor.insAtDirEnd(-dir, this[dir]);
- else cursor.insDirOf(dir, this.parent);
- };
- _.selectOutOf = function(dir, cursor) {
- cursor.insDirOf(dir, this.parent);
- };
- _.deleteOutOf = function(dir, cursor) {
- cursor.unwrapGramp();
- };
- _.seek = function(pageX, cursor) {
- var node = this.ends[R];
- if (!node || node.jQ.offset().left + node.jQ.outerWidth() < pageX) {
- return cursor.insAtRightEnd(this);
- }
- if (pageX < this.ends[L].jQ.offset().left) return cursor.insAtLeftEnd(this);
- while (pageX < node.jQ.offset().left) node = node[L];
- return node.seek(pageX, cursor);
- };
- _.chToCmd = function(ch) {
- var cons;
- // exclude f because it gets a dedicated command with more spacing
- if (ch.match(/^[a-eg-zA-Z]$/))
- return Letter(ch);
- else if (/^\d$/.test(ch))
- return Digit(ch);
- else if (cons = CharCmds[ch] || LatexCmds[ch])
- return cons(ch);
- else
- return VanillaSymbol(ch);
- };
- _.write = function(cursor, ch) {
- var cmd = this.chToCmd(ch);
- if (cursor.selection) cmd.replaces(cursor.replaceSelection());
- cmd.createLeftOf(cursor.show());
- };
- _.focus = function() {
- this.jQ.addClass('mq-hasCursor');
- this.jQ.removeClass('mq-empty');
- return this;
- };
- _.blur = function() {
- this.jQ.removeClass('mq-hasCursor');
- if (this.isEmpty())
- this.jQ.addClass('mq-empty');
- return this;
- };
- });
- API.StaticMath = function(APIClasses) {
- return P(APIClasses.AbstractMathQuill, function(_, super_) {
- this.RootBlock = MathBlock;
- _.__mathquillify = function() {
- super_.__mathquillify.call(this, 'mq-math-mode');
- this.__controller.delegateMouseEvents();
- this.__controller.staticMathTextareaEvents();
- return this;
- };
- _.init = function() {
- super_.init.apply(this, arguments);
- this.__controller.root.postOrder(
- 'registerInnerField', this.innerFields = [], APIClasses.MathField);
- };
- _.latex = function() {
- var returned = super_.latex.apply(this, arguments);
- if (arguments.length > 0) {
- this.__controller.root.postOrder(
- 'registerInnerField', this.innerFields = [], APIClasses.MathField);
- }
- return returned;
- };
- });
- };
- var RootMathBlock = P(MathBlock, RootBlockMixin);
- API.MathField = function(APIClasses) {
- return P(APIClasses.EditableField, function(_, super_) {
- this.RootBlock = RootMathBlock;
- _.__mathquillify = function(opts, interfaceVersion) {
- this.config(opts);
- if (interfaceVersion > 1) this.__controller.root.reflow = noop;
- super_.__mathquillify.call(this, 'mq-editable-field mq-math-mode');
- delete this.__controller.root.reflow;
- return this;
- };
- });
- };
- /*********************************
- * Symbols for Basic Mathematics
- ********************************/
- var Digit = P(VanillaSymbol, function(_, super_) {
- _.createLeftOf = function(cursor) {
- if (cursor.options.autoSubscriptNumerals
- && cursor.parent !== cursor.parent.parent.sub
- && ((cursor[L] instanceof Variable && cursor[L].isItalic !== false)
- || (cursor[L] instanceof SupSub
- && cursor[L][L] instanceof Variable
- && cursor[L][L].isItalic !== false))) {
- LatexCmds._().createLeftOf(cursor);
- super_.createLeftOf.call(this, cursor);
- cursor.insRightOf(cursor.parent.parent);
- }
- else super_.createLeftOf.call(this, cursor);
- };
- });
- var Variable = P(Symbol, function(_, super_) {
- _.init = function(ch, html) {
- super_.init.call(this, ch, '<var>'+(html || ch)+'</var>');
- };
- _.text = function() {
- var text = this.ctrlSeq;
- if (this[L] && !(this[L] instanceof Variable)
- && !(this[L] instanceof BinaryOperator)
- && this[L].ctrlSeq !== "\\ ")
- text = '*' + text;
- if (this[R] && !(this[R] instanceof BinaryOperator)
- && !(this[R] instanceof SupSub))
- text += '*';
- return text;
- };
- });
- Options.p.autoCommands = { _maxLength: 0 };
- optionProcessors.autoCommands = function(cmds) {
- if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
- throw '"'+cmds+'" not a space-delimited list of only letters';
- }
- var list = cmds.split(' '), dict = {}, maxLength = 0;
- for (var i = 0; i < list.length; i += 1) {
- var cmd = list[i];
- if (cmd.length < 2) {
- throw 'autocommand "'+cmd+'" not minimum length of 2';
- }
- if (LatexCmds[cmd] === OperatorName) {
- throw '"' + cmd + '" is a built-in operator name';
- }
- dict[cmd] = 1;
- maxLength = max(maxLength, cmd.length);
- }
- dict._maxLength = maxLength;
- return dict;
- };
- var Letter = P(Variable, function(_, super_) {
- _.init = function(ch) { return super_.init.call(this, this.letter = ch); };
- _.createLeftOf = function(cursor) {
- var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
- if (maxLength > 0) {
- // want longest possible autocommand, so join together longest
- // sequence of letters
- var str = this.letter, l = cursor[L], i = 1;
- while (l instanceof Letter && i < maxLength) {
- str = l.letter + str, l = l[L], i += 1;
- }
- // check for an autocommand, going thru substrings longest to shortest
- while (str.length) {
- if (autoCmds.hasOwnProperty(str)) {
- for (var i = 2, l = cursor[L]; i < str.length; i += 1, l = l[L]);
- Fragment(l, cursor[L]).remove();
- cursor[L] = l[L];
- return LatexCmds[str](str).createLeftOf(cursor);
- }
- str = str.slice(1);
- }
- }
- super_.createLeftOf.apply(this, arguments);
- };
- _.italicize = function(bool) {
- this.isItalic = bool;
- this.jQ.toggleClass('mq-operator-name', !bool);
- return this;
- };
- _.finalizeTree = _.siblingDeleted = _.siblingCreated = function(opts, dir) {
- // don't auto-un-italicize if the sibling to my right changed (dir === R or
- // undefined) and it's now a Letter, it will un-italicize everyone
- if (dir !== L && this[R] instanceof Letter) return;
- this.autoUnItalicize(opts);
- };
- _.autoUnItalicize = function(opts) {
- var autoOps = opts.autoOperatorNames;
- if (autoOps._maxLength === 0) return;
- // want longest possible operator names, so join together entire contiguous
- // sequence of letters
- var str = this.letter;
- for (var l = this[L]; l instanceof Letter; l = l[L]) str = l.letter + str;
- for (var r = this[R]; r instanceof Letter; r = r[R]) str += r.letter;
- // removeClass and delete flags from all letters before figuring out
- // which, if any, are part of an operator name
- Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
- el.italicize(true).jQ.removeClass('mq-first mq-last');
- el.ctrlSeq = el.letter;
- });
- // check for operator names: at each position from left to right, check
- // substrings from longest to shortest
- outer: for (var i = 0, first = l[R] || this.parent.ends[L]; i < str.length; i += 1, first = first[R]) {
- for (var len = min(autoOps._maxLength, str.length - i); len > 0; len -= 1) {
- var word = str.slice(i, i + len);
- if (autoOps.hasOwnProperty(word)) {
- for (var j = 0, letter = first; j < len; j += 1, letter = letter[R]) {
- letter.italicize(false);
- var last = letter;
- }
- var isBuiltIn = BuiltInOpNames.hasOwnProperty(word);
- first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
- last.ctrlSeq += (isBuiltIn ? ' ' : '}');
- if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
- if (nonOperatorSymbol(first[L])) first.jQ.addClass('mq-first');
- if (nonOperatorSymbol(last[R])) last.jQ.addClass('mq-last');
- i += len - 1;
- first = last;
- continue outer;
- }
- }
- }
- };
- function nonOperatorSymbol(node) {
- return node instanceof Symbol && !(node instanceof BinaryOperator);
- }
- });
- var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
- // are built-into LaTeX: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
- // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
- // and 'arsinh', which must be exported as \operatorname{hcf} and
- // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
- // \varlimsup are not supported
- var AutoOpNames = Options.p.autoOperatorNames = { _maxLength: 9 }; // the set
- // of operator names that MathQuill auto-unitalicizes by default; overridable
- var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 };
- (function() {
- var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup'
- + ' limsup liminf injlim projlim Pr').split(' ');
- for (var i = 0; i < mostOps.length; i += 1) {
- BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1;
- }
- var builtInTrigs = // why coth but not sech and csch, LaTeX?
- 'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' ');
- for (var i = 0; i < builtInTrigs.length; i += 1) {
- BuiltInOpNames[builtInTrigs[i]] = 1;
- }
- var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' ');
- for (var i = 0; i < autoTrigs.length; i += 1) {
- AutoOpNames[autoTrigs[i]] =
- AutoOpNames['arc'+autoTrigs[i]] =
- AutoOpNames[autoTrigs[i]+'h'] =
- AutoOpNames['ar'+autoTrigs[i]+'h'] =
- AutoOpNames['arc'+autoTrigs[i]+'h'] = 1;
- }
- // compat with some of the nonstandard LaTeX exported by MathQuill
- // before #247. None of these are real LaTeX commands so, seems safe
- var moreNonstandardOps = 'gcf hcf lcm proj span'.split(' ');
- for (var i = 0; i < moreNonstandardOps.length; i += 1) {
- AutoOpNames[moreNonstandardOps[i]] = 1;
- }
- }());
- optionProcessors.autoOperatorNames = function(cmds) {
- if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
- throw '"'+cmds+'" not a space-delimited list of only letters';
- }
- var list = cmds.split(' '), dict = {}, maxLength = 0;
- for (var i = 0; i < list.length; i += 1) {
- var cmd = list[i];
- if (cmd.length < 2) {
- throw '"'+cmd+'" not minimum length of 2';
- }
- dict[cmd] = 1;
- maxLength = max(maxLength, cmd.length);
- }
- dict._maxLength = maxLength;
- return dict;
- };
- var OperatorName = P(Symbol, function(_, super_) {
- _.init = function(fn) { this.ctrlSeq = fn; };
- _.createLeftOf = function(cursor) {
- var fn = this.ctrlSeq;
- for (var i = 0; i < fn.length; i += 1) {
- Letter(fn.charAt(i)).createLeftOf(cursor);
- }
- };
- _.parser = function() {
- var fn = this.ctrlSeq;
- var block = MathBlock();
- for (var i = 0; i < fn.length; i += 1) {
- Letter(fn.charAt(i)).adopt(block, block.ends[R], 0);
- }
- return Parser.succeed(block.children());
- };
- });
- for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) {
- LatexCmds[fn] = OperatorName;
- }
- LatexCmds.operatorname = P(MathCommand, function(_) {
- _.createLeftOf = noop;
- _.numBlocks = function() { return 1; };
- _.parser = function() {
- return latexMathParser.block.map(function(b) { return b.children(); });
- };
- });
- LatexCmds.f = P(Letter, function(_, super_) {
- _.init = function() {
- Symbol.p.init.call(this, this.letter = 'f', '<var class="mq-f">f</var>');
- };
- _.italicize = function(bool) {
- this.jQ.html('f').toggleClass('mq-f', bool);
- return super_.italicize.apply(this, arguments);
- };
- });
- // VanillaSymbol's
- LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', ' ');
- LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '′');
- LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
- if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash;
- LatexCmds.$ = bind(VanillaSymbol, '\\$', '$');
- // does not use Symbola font
- var NonSymbolaSymbol = P(Symbol, function(_, super_) {
- _.init = function(ch, html) {
- super_.init.call(this, ch, '<span class="mq-nonSymbola">'+(html || ch)+'</span>');
- };
- });
- LatexCmds['@'] = NonSymbolaSymbol;
- LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&');
- LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
- //the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html
- //lowercase Greek letter variables
- LatexCmds.alpha =
- LatexCmds.beta =
- LatexCmds.gamma =
- LatexCmds.delta =
- LatexCmds.zeta =
- LatexCmds.eta =
- LatexCmds.theta =
- LatexCmds.iota =
- LatexCmds.kappa =
- LatexCmds.mu =
- LatexCmds.nu =
- LatexCmds.xi =
- LatexCmds.rho =
- LatexCmds.sigma =
- LatexCmds.tau =
- LatexCmds.chi =
- LatexCmds.psi =
- LatexCmds.omega = P(Variable, function(_, super_) {
- _.init = function(latex) {
- super_.init.call(this,'\\'+latex+' ','&'+latex+';');
- };
- });
- //why can't anybody FUCKING agree on these
- LatexCmds.phi = //W3C or Unicode?
- bind(Variable,'\\phi ','ϕ');
- LatexCmds.phiv = //Elsevier and 9573-13
- LatexCmds.varphi = //AMS and LaTeX
- bind(Variable,'\\varphi ','φ');
- LatexCmds.epsilon = //W3C or Unicode?
- bind(Variable,'\\epsilon ','ϵ');
- LatexCmds.epsiv = //Elsevier and 9573-13
- LatexCmds.varepsilon = //AMS and LaTeX
- bind(Variable,'\\varepsilon ','ε');
- LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13
- LatexCmds.varpi = //AMS and LaTeX
- bind(Variable,'\\varpi ','ϖ');
- LatexCmds.sigmaf = //W3C/Unicode
- LatexCmds.sigmav = //Elsevier
- LatexCmds.varsigma = //LaTeX
- bind(Variable,'\\varsigma ','ς');
- LatexCmds.thetav = //Elsevier and 9573-13
- LatexCmds.vartheta = //AMS and LaTeX
- LatexCmds.thetasym = //W3C/Unicode
- bind(Variable,'\\vartheta ','ϑ');
- LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode
- LatexCmds.upsi = //Elsevier and 9573-13
- bind(Variable,'\\upsilon ','υ');
- //these aren't even mentioned in the HTML character entity references
- LatexCmds.gammad = //Elsevier
- LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above)
- LatexCmds.digamma = //LaTeX
- bind(Variable,'\\digamma ','ϝ');
- LatexCmds.kappav = //Elsevier
- LatexCmds.varkappa = //AMS and LaTeX
- bind(Variable,'\\varkappa ','ϰ');
- LatexCmds.rhov = //Elsevier and 9573-13
- LatexCmds.varrho = //AMS and LaTeX
- bind(Variable,'\\varrho ','ϱ');
- //Greek constants, look best in non-italicized Times New Roman
- LatexCmds.pi = LatexCmds['\u03c0'] = bind(NonSymbolaSymbol,'\\pi ','π');
- LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','λ');
- //uppercase greek letters
- LatexCmds.Upsilon = //LaTeX
- LatexCmds.Upsi = //Elsevier and 9573-13
- LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
- LatexCmds.Upsih = //'cos it makes sense to me
- bind(Symbol,'\\Upsilon ','<var style="font-family: serif">ϒ</var>'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :(
- //other symbols with the same LaTeX command and HTML character entity reference
- LatexCmds.Gamma =
- LatexCmds.Delta =
- LatexCmds.Theta =
- LatexCmds.Lambda =
- LatexCmds.Xi =
- LatexCmds.Pi =
- LatexCmds.Sigma =
- LatexCmds.Phi =
- LatexCmds.Psi =
- LatexCmds.Omega =
- LatexCmds.forall = P(VanillaSymbol, function(_, super_) {
- _.init = function(latex) {
- super_.init.call(this,'\\'+latex+' ','&'+latex+';');
- };
- });
- // symbols that aren't a single MathCommand, but are instead a whole
- // Fragment. Creates the Fragment from a LaTeX string
- var LatexFragment = P(MathCommand, function(_) {
- _.init = function(latex) { this.latex = latex; };
- _.createLeftOf = function(cursor) {
- var block = latexMathParser.parse(this.latex);
- block.children().adopt(cursor.parent, cursor[L], cursor[R]);
- cursor[L] = block.ends[R];
- block.jQize().insertBefore(cursor.jQ);
- block.finalizeInsert(cursor.options, cursor);
- if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
- if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
- cursor.parent.bubble('reflow');
- };
- _.parser = function() {
- var frag = latexMathParser.parse(this.latex).children();
- return Parser.succeed(frag);
- };
- });
- // for what seems to me like [stupid reasons][1], Unicode provides
- // subscripted and superscripted versions of all ten Arabic numerals,
- // as well as [so-called "vulgar fractions"][2].
- // Nobody really cares about most of them, but some of them actually
- // predate Unicode, dating back to [ISO-8859-1][3], apparently also
- // known as "Latin-1", which among other things [Windows-1252][4]
- // largely coincides with, so Microsoft Word sometimes inserts them
- // and they get copy-pasted into MathQuill.
- //
- // (Irrelevant but funny story: though not a superset of Latin-1 aka
- // ISO-8859-1, Windows-1252 **is** a strict superset of the "closely
- // related but distinct"[3] "ISO 8859-1" -- see the lack of a dash
- // after "ISO"? Completely different character set, like elephants vs
- // elephant seals, or "Zombies" vs "Zombie Redneck Torture Family".
- // What kind of idiot would get them confused.
- // People in fact got them confused so much, it was so common to
- // mislabel Windows-1252 text as ISO-8859-1, that most modern web
- // browsers and email clients treat the MIME charset of ISO-8859-1
- // as actually Windows-1252, behavior now standard in the HTML5 spec.)
- //
- // [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_andsuper_scripts
- // [2]: http://en.wikipedia.org/wiki/Number_Forms
- // [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
- // [4]: http://en.wikipedia.org/wiki/Windows-1252
- LatexCmds['\u00b9'] = bind(LatexFragment, '^1');
- LatexCmds['\u00b2'] = bind(LatexFragment, '^2');
- LatexCmds['\u00b3'] = bind(LatexFragment, '^3');
- LatexCmds['\u00bc'] = bind(LatexFragment, '\\frac14');
- LatexCmds['\u00bd'] = bind(LatexFragment, '\\frac12');
- LatexCmds['\u00be'] = bind(LatexFragment, '\\frac34');
- var PlusMinus = P(BinaryOperator, function(_) {
- _.init = VanillaSymbol.prototype.init;
- _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
- if (dir === R) return; // ignore if sibling only changed on the right
- this.jQ[0].className =
- (!this[L] || this[L] instanceof BinaryOperator ? '' : 'mq-binary-operator');
- return this;
- };
- });
- LatexCmds['+'] = bind(PlusMinus, '+', '+');
- //yes, these are different dashes, I think one is an en dash and the other is a hyphen
- LatexCmds['\u2013'] = LatexCmds['-'] = bind(PlusMinus, '-', '−');
- LatexCmds['\u00b1'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus =
- bind(PlusMinus,'\\pm ','±');
- LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus =
- bind(PlusMinus,'\\mp ','∓');
- CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
- bind(BinaryOperator, '\\cdot ', '·', '*');
- //semantically should be ⋅, but · looks better
- var Inequality = P(BinaryOperator, function(_, super_) {
- _.init = function(data, strict) {
- this.data = data;
- this.strict = strict;
- var strictness = (strict ? 'Strict' : '');
- super_.init.call(this, data['ctrlSeq'+strictness], data['html'+strictness],
- data['text'+strictness]);
- };
- _.swap = function(strict) {
- this.strict = strict;
- var strictness = (strict ? 'Strict' : '');
- this.ctrlSeq = this.data['ctrlSeq'+strictness];
- this.jQ.html(this.data['html'+strictness]);
- this.textTemplate = [ this.data['text'+strictness] ];
- };
- _.deleteTowards = function(dir, cursor) {
- if (dir === L && !this.strict) {
- this.swap(true);
- this.bubble('reflow');
- return;
- }
- super_.deleteTowards.apply(this, arguments);
- };
- });
- var less = { ctrlSeq: '\\le ', html: '≤', text: '\u2264',
- ctrlSeqStrict: '<', htmlStrict: '<', textStrict: '<' };
- var greater = { ctrlSeq: '\\ge ', html: '≥', text: '\u2265',
- ctrlSeqStrict: '>', htmlStrict: '>', textStrict: '>' };
- LatexCmds['<'] = LatexCmds.lt = bind(Inequality, less, true);
- LatexCmds['>'] = LatexCmds.gt = bind(Inequality, greater, true);
- LatexCmds['\u2264'] = LatexCmds.le = LatexCmds.leq = bind(Inequality, less, false);
- LatexCmds['\u2265'] = LatexCmds.ge = LatexCmds.geq = bind(Inequality, greater, false);
- var Equality = P(BinaryOperator, function(_, super_) {
- _.init = function() {
- super_.init.call(this, '=', '=');
- };
- _.createLeftOf = function(cursor) {
- if (cursor[L] instanceof Inequality && cursor[L].strict) {
- cursor[L].swap(false);
- cursor[L].bubble('reflow');
- return;
- }
- super_.createLeftOf.apply(this, arguments);
- };
- });
- LatexCmds['='] = Equality;
- LatexCmds['\u00d7'] = LatexCmds.times = bind(BinaryOperator, '\\times ', '×', '[x]');
- LatexCmds['\u00f7'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides =
- bind(BinaryOperator,'\\div ','÷', '[/]');
- CharCmds['~'] = LatexCmds.sim = bind(BinaryOperator, '\\sim ', '~', '~');
- /***************************
- * Commands and Operators.
- **************************/
- var scale, // = function(jQ, x, y) { ... }
- //will use a CSS 2D transform to scale the jQuery-wrapped HTML elements,
- //or the filter matrix transform fallback for IE 5.5-8, or gracefully degrade to
- //increasing the fontSize to match the vertical Y scaling factor.
- //ideas from http://github.com/louisremi/jquery.transform.js
- //see also http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx
- forceIERedraw = noop,
- div = document.createElement('div'),
- div_style = div.style,
- transformPropNames = {
- transform:1,
- WebkitTransform:1,
- MozTransform:1,
- OTransform:1,
- msTransform:1
- },
- transformPropName;
- for (var prop in transformPropNames) {
- if (prop in div_style) {
- transformPropName = prop;
- break;
- }
- }
- if (transformPropName) {
- scale = function(jQ, x, y) {
- jQ.css(transformPropName, 'scale('+x+','+y+')');
- };
- }
- else if ('filter' in div_style) { //IE 6, 7, & 8 fallback, see https://github.com/laughinghan/mathquill/wiki/Transforms
- forceIERedraw = function(el){ el.className = el.className; };
- scale = function(jQ, x, y) { //NOTE: assumes y > x
- x /= (1+(y-1)/2);
- jQ.css('fontSize', y + 'em');
- if (!jQ.hasClass('mq-matrixed-container')) {
- jQ.addClass('mq-matrixed-container')
- .wrapInner('<span class="mq-matrixed"></span>');
- }
- var innerjQ = jQ.children()
- .css('filter', 'progid:DXImageTransform.Microsoft'
- + '.Matrix(M11=' + x + ",SizingMethod='auto expand')"
- );
- function calculateMarginRight() {
- jQ.css('marginRight', (innerjQ.width()-1)*(x-1)/x + 'px');
- }
- calculateMarginRight();
- var intervalId = setInterval(calculateMarginRight);
- $(window).load(function() {
- clearTimeout(intervalId);
- calculateMarginRight();
- });
- };
- }
- else {
- scale = function(jQ, x, y) {
- jQ.css('fontSize', y + 'em');
- };
- }
- var Style = P(MathCommand, function(_, super_) {
- _.init = function(ctrlSeq, tagName, attrs) {
- super_.init.call(this, ctrlSeq, '<'+tagName+' '+attrs+'>&0</'+tagName+'>');
- };
- });
- //fonts
- LatexCmds.mathrm = bind(Style, '\\mathrm', 'span', 'class="mq-roman mq-font"');
- LatexCmds.mathit = bind(Style, '\\mathit', 'i', 'class="mq-font"');
- LatexCmds.mathbf = bind(Style, '\\mathbf', 'b', 'class="mq-font"');
- LatexCmds.mathsf = bind(Style, '\\mathsf', 'span', 'class="mq-sans-serif mq-font"');
- LatexCmds.mathtt = bind(Style, '\\mathtt', 'span', 'class="mq-monospace mq-font"');
- //text-decoration
- LatexCmds.underline = bind(Style, '\\underline', 'span', 'class="mq-non-leaf mq-underline"');
- LatexCmds.overline = LatexCmds.bar = bind(Style, '\\overline', 'span', 'class="mq-non-leaf mq-overline"');
- LatexCmds.overrightarrow = bind(Style, '\\overrightarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-right"');
- LatexCmds.overleftarrow = bind(Style, '\\overleftarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-left"');
- // `\textcolor{color}{math}` will apply a color to the given math content, where
- // `color` is any valid CSS Color Value (see [SitePoint docs][] (recommended),
- // [Mozilla docs][], or [W3C spec][]).
- //
- // [SitePoint docs]: http://reference.sitepoint.com/css/colorvalues
- // [Mozilla docs]: https://developer.mozilla.org/en-US/docs/CSS/color_value#Values
- // [W3C spec]: http://dev.w3.org/csswg/css3-color/#colorunits
- var TextColor = LatexCmds.textcolor = P(MathCommand, function(_, super_) {
- _.setColor = function(color) {
- this.color = color;
- this.htmlTemplate =
- '<span class="mq-textcolor" style="color:' + color + '">&0</span>';
- };
- _.latex = function() {
- return '\\textcolor{' + this.color + '}{' + this.blocks[0].latex() + '}';
- };
- _.parser = function() {
- var self = this;
- var optWhitespace = Parser.optWhitespace;
- var string = Parser.string;
- var regex = Parser.regex;
- return optWhitespace
- .then(string('{'))
- .then(regex(/^[#\w\s.,()%-]*/))
- .skip(string('}'))
- .then(function(color) {
- self.setColor(color);
- return super_.parser.call(self);
- })
- ;
- };
- });
- // Very similar to the \textcolor command, but will add the given CSS class.
- // Usage: \class{classname}{math}
- // Note regex that whitelists valid CSS classname characters:
- // https://github.com/mathquill/mathquill/pull/191#discussion_r4327442
- var Class = LatexCmds['class'] = P(MathCommand, function(_, super_) {
- _.parser = function() {
- var self = this, string = Parser.string, regex = Parser.regex;
- return Parser.optWhitespace
- .then(string('{'))
- .then(regex(/^[-\w\s\\\xA0-\xFF]*/))
- .skip(string('}'))
- .then(function(cls) {
- self.htmlTemplate = '<span class="mq-class '+cls+'">&0</span>';
- return super_.parser.call(self);
- })
- ;
- };
- });
- var SupSub = P(MathCommand, function(_, super_) {
- _.ctrlSeq = '_{...}^{...}';
- _.createLeftOf = function(cursor) {
- if (!cursor[L] && cursor.options.supSubsRequireOperand) return;
- return super_.createLeftOf.apply(this, arguments);
- };
- _.contactWeld = function(cursor) {
- // Look on either side for a SupSub, if one is found compare my
- // .sub, .sup with its .sub, .sup. If I have one that it doesn't,
- // then call .addBlock() on it with my block; if I have one that
- // it also has, then insert my block's children into its block,
- // unless my block has none, in which case insert the cursor into
- // its block (and not mine, I'm about to remove myself) in the case
- // I was just typed.
- // TODO: simplify
- // equiv. to [L, R].forEach(function(dir) { ... });
- for (var dir = L; dir; dir = (dir === L ? R : false)) {
- if (this[dir] instanceof SupSub) {
- // equiv. to 'sub sup'.split(' ').forEach(function(supsub) { ... });
- for (var supsub = 'sub'; supsub; supsub = (supsub === 'sub' ? 'sup' : false)) {
- var src = this[supsub], dest = this[dir][supsub];
- if (!src) continue;
- if (!dest) this[dir].addBlock(src.disown());
- else if (!src.isEmpty()) { // ins src children at -dir end of dest
- src.jQ.children().insAtDirEnd(-dir, dest.jQ);
- var children = src.children().disown();
- var pt = Point(dest, children.ends[R], dest.ends[L]);
- if (dir === L) children.adopt(dest, dest.ends[R], 0);
- else children.adopt(dest, 0, dest.ends[L]);
- }
- else var pt = Point(dest, 0, dest.ends[L]);
- this.placeCursor = (function(dest, src) { // TODO: don't monkey-patch
- return function(cursor) { cursor.insAtDirEnd(-dir, dest || src); };
- }(dest, src));
- }
- this.remove();
- if (cursor && cursor[L] === this) {
- if (dir === R && pt) {
- pt[L] ? cursor.insRightOf(pt[L]) : cursor.insAtLeftEnd(pt.parent);
- }
- else cursor.insRightOf(this[dir]);
- }
- break;
- }
- }
- this.respace();
- };
- Options.p.charsThatBreakOutOfSupSub = '';
- _.finalizeTree = function() {
- this.ends[L].write = function(cursor, ch) {
- if (cursor.options.autoSubscriptNumerals && this === this.parent.sub) {
- if (ch === '_') return;
- var cmd = this.chToCmd(ch);
- if (cmd instanceof Symbol) cursor.deleteSelection();
- else cursor.clearSelection().insRightOf(this.parent);
- return cmd.createLeftOf(cursor.show());
- }
- if (cursor[L] && !cursor[R] && !cursor.selection
- && cursor.options.charsThatBreakOutOfSupSub.indexOf(ch) > -1) {
- cursor.insRightOf(this.parent);
- }
- MathBlock.p.write.apply(this, arguments);
- };
- };
- _.moveTowards = function(dir, cursor, updown) {
- if (cursor.options.autoSubscriptNumerals && !this.sup) {
- cursor.insDirOf(dir, this);
- }
- else super_.moveTowards.apply(this, arguments);
- };
- _.deleteTowards = function(dir, cursor) {
- if (cursor.options.autoSubscriptNumerals && this.sub) {
- var cmd = this.sub.ends[-dir];
- if (cmd instanceof Symbol) cmd.remove();
- else if (cmd) cmd.deleteTowards(dir, cursor.insAtDirEnd(-dir, this.sub));
- // TODO: factor out a .removeBlock() or something
- if (this.sub.isEmpty()) {
- this.sub.deleteOutOf(L, cursor.insAtLeftEnd(this.sub));
- if (this.sup) cursor.insDirOf(-dir, this);
- // Note `-dir` because in e.g. x_1^2| want backspacing (leftward)
- // to delete the 1 but to end up rightward of x^2; with non-negated
- // `dir` (try it), the cursor appears to have gone "through" the ^2.
- }
- }
- else super_.deleteTowards.apply(this, arguments);
- };
- _.latex = function() {
- function latex(prefix, block) {
- var l = block && block.latex();
- return block ? prefix + (l.length === 1 ? l : '{' + (l || ' ') + '}') : '';
- }
- return latex('_', this.sub) + latex('^', this.sup);
- };
- _.respace = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
- if (dir === R) return; // ignore if sibling only changed on the right
- this.jQ.toggleClass('mq-limit', this[L].ctrlSeq === '\\int ');
- };
- _.addBlock = function(block) {
- if (this.supsub === 'sub') {
- this.sup = this.upInto = this.sub.upOutOf = block;
- block.adopt(this, this.sub, 0).downOutOf = this.sub;
- block.jQ = $('<span class="mq-sup"/>').append(block.jQ.children())
- .attr(mqBlockId, block.id).prependTo(this.jQ);
- }
- else {
- this.sub = this.downInto = this.sup.downOutOf = block;
- block.adopt(this, 0, this.sup).upOutOf = this.sup;
- block.jQ = $('<span class="mq-sub"></span>').append(block.jQ.children())
- .attr(mqBlockId, block.id).appendTo(this.jQ.removeClass('mq-sup-only'));
- this.jQ.append('<span style="display:inline-block;width:0">​</span>');
- }
- // like 'sub sup'.split(' ').forEach(function(supsub) { ... });
- for (var i = 0; i < 2; i += 1) (function(cmd, supsub, oppositeSupsub, updown) {
- cmd[supsub].deleteOutOf = function(dir, cursor) {
- cursor.insDirOf((this[dir] ? -dir : dir), this.parent);
- if (!this.isEmpty()) {
- var end = this.ends[dir];
- this.children().disown()
- .withDirAdopt(dir, cursor.parent, cursor[dir], cursor[-dir])
- .jQ.insDirOf(-dir, cursor.jQ);
- cursor[-dir] = end;
- }
- cmd.supsub = oppositeSupsub;
- delete cmd[supsub];
- delete cmd[updown+'Into'];
- cmd[oppositeSupsub][updown+'OutOf'] = insLeftOfMeUnlessAtEnd;
- delete cmd[oppositeSupsub].deleteOutOf;
- if (supsub === 'sub') $(cmd.jQ.addClass('mq-sup-only')[0].lastChild).remove();
- this.remove();
- };
- }(this, 'sub sup'.split(' ')[i], 'sup sub'.split(' ')[i], 'down up'.split(' ')[i]));
- };
- });
- function insLeftOfMeUnlessAtEnd(cursor) {
- // cursor.insLeftOf(cmd), unless cursor at the end of block, and every
- // ancestor cmd is at the end of every ancestor block
- var cmd = this.parent, ancestorCmd = cursor;
- do {
- if (ancestorCmd[R]) return cursor.insLeftOf(cmd);
- ancestorCmd = ancestorCmd.parent.parent;
- } while (ancestorCmd !== cmd);
- cursor.insRightOf(cmd);
- }
- LatexCmds.subscript =
- LatexCmds._ = P(SupSub, function(_, super_) {
- _.supsub = 'sub';
- _.htmlTemplate =
- '<span class="mq-supsub mq-non-leaf">'
- + '<span class="mq-sub">&0</span>'
- + '<span style="display:inline-block;width:0">​</span>'
- + '</span>'
- ;
- _.textTemplate = [ '_' ];
- _.finalizeTree = function() {
- this.downInto = this.sub = this.ends[L];
- this.sub.upOutOf = insLeftOfMeUnlessAtEnd;
- super_.finalizeTree.call(this);
- };
- });
- LatexCmds.superscript =
- LatexCmds.supscript =
- LatexCmds['^'] = P(SupSub, function(_, super_) {
- _.supsub = 'sup';
- _.htmlTemplate =
- '<span class="mq-supsub mq-non-leaf mq-sup-only">'
- + '<span class="mq-sup">&0</span>'
- + '</span>'
- ;
- _.textTemplate = [ '^' ];
- _.finalizeTree = function() {
- this.upInto = this.sup = this.ends[R];
- this.sup.downOutOf = insLeftOfMeUnlessAtEnd;
- super_.finalizeTree.call(this);
- };
- });
- var SummationNotation = P(MathCommand, function(_, super_) {
- _.init = function(ch, html) {
- var htmlTemplate =
- '<span class="mq-large-operator mq-non-leaf">'
- + '<span class="mq-to"><span>&1</span></span>'
- + '<big>'+html+'</big>'
- + '<span class="mq-from"><span>&0</span></span>'
- + '</span>'
- ;
- Symbol.prototype.init.call(this, ch, htmlTemplate);
- };
- _.createLeftOf = function(cursor) {
- super_.createLeftOf.apply(this, arguments);
- if (cursor.options.sumStartsWithNEquals) {
- Letter('n').createLeftOf(cursor);
- Equality().createLeftOf(cursor);
- }
- };
- _.latex = function() {
- function simplify(latex) {
- return latex.length === 1 ? latex : '{' + (latex || ' ') + '}';
- }
- return this.ctrlSeq + '_' + simplify(this.ends[L].latex()) +
- '^' + simplify(this.ends[R].latex());
- };
- _.parser = function() {
- var string = Parser.string;
- var optWhitespace = Parser.optWhitespace;
- var succeed = Parser.succeed;
- var block = latexMathParser.block;
- var self = this;
- var blocks = self.blocks = [ MathBlock(), MathBlock() ];
- for (var i = 0; i < blocks.length; i += 1) {
- blocks[i].adopt(self, self.ends[R], 0);
- }
- return optWhitespace.then(string('_').or(string('^'))).then(function(supOrSub) {
- var child = blocks[supOrSub === '_' ? 0 : 1];
- return block.then(function(block) {
- block.children().adopt(child, child.ends[R], 0);
- return succeed(self);
- });
- }).many().result(self);
- };
- _.finalizeTree = function() {
- this.downInto = this.ends[L];
- this.upInto = this.ends[R];
- this.ends[L].upOutOf = this.ends[R];
- this.ends[R].downOutOf = this.ends[L];
- };
- });
- LatexCmds['\u2211'] =
- LatexCmds.sum =
- LatexCmds.summation = bind(SummationNotation,'\\sum ','∑');
- LatexCmds['\u220f'] =
- LatexCmds.prod =
- LatexCmds.product = bind(SummationNotation,'\\prod ','∏');
- LatexCmds.coprod =
- LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','∐');
- var Fraction =
- LatexCmds.frac =
- LatexCmds.dfrac =
- LatexCmds.cfrac =
- LatexCmds.fraction = P(MathCommand, function(_, super_) {
- _.ctrlSeq = '\\frac';
- _.htmlTemplate =
- '<span class="mq-fraction mq-non-leaf">'
- + '<span class="mq-numerator">&0</span>'
- + '<span class="mq-denominator">&1</span>'
- + '<span style="display:inline-block;width:0">​</span>'
- + '</span>'
- ;
- _.textTemplate = ['(', ')/(', ')'];
- _.finalizeTree = function() {
- this.upInto = this.ends[R].upOutOf = this.ends[L];
- this.downInto = this.ends[L].downOutOf = this.ends[R];
- };
- });
- var LiveFraction =
- LatexCmds.over =
- CharCmds['/'] = P(Fraction, function(_, super_) {
- _.createLeftOf = function(cursor) {
- if (!this.replacedFragment) {
- var leftward = cursor[L];
- while (leftward &&
- !(
- leftward instanceof BinaryOperator ||
- leftward instanceof (LatexCmds.text || noop) ||
- leftward instanceof SummationNotation ||
- leftward.ctrlSeq === '\\ ' ||
- /^[,;:]$/.test(leftward.ctrlSeq)
- ) //lookbehind for operator
- ) leftward = leftward[L];
- if (leftward instanceof SummationNotation && leftward[R] instanceof SupSub) {
- leftward = leftward[R];
- if (leftward[R] instanceof SupSub && leftward[R].ctrlSeq != leftward.ctrlSeq)
- leftward = leftward[R];
- }
- if (leftward !== cursor[L]) {
- this.replaces(Fragment(leftward[R] || cursor.parent.ends[L], cursor[L]));
- cursor[L] = leftward;
- }
- }
- super_.createLeftOf.call(this, cursor);
- };
- });
- var SquareRoot =
- LatexCmds.sqrt =
- LatexCmds['\u221a'] = P(MathCommand, function(_, super_) {
- _.ctrlSeq = '\\sqrt';
- _.htmlTemplate =
- '<span class="mq-non-leaf">'
- + '<span class="mq-scaled mq-sqrt-prefix">√</span>'
- + '<span class="mq-non-leaf mq-sqrt-stem">&0</span>'
- + '</span>'
- ;
- _.textTemplate = ['sqrt(', ')'];
- _.parser = function() {
- return latexMathParser.optBlock.then(function(optBlock) {
- return latexMathParser.block.map(function(block) {
- var nthroot = NthRoot();
- nthroot.blocks = [ optBlock, block ];
- optBlock.adopt(nthroot, 0, 0);
- block.adopt(nthroot, optBlock, 0);
- return nthroot;
- });
- }).or(super_.parser.call(this));
- };
- _.reflow = function() {
- var block = this.ends[R].jQ;
- scale(block.prev(), 1, block.innerHeight()/+block.css('fontSize').slice(0,-2) - .1);
- };
- });
- var Vec = LatexCmds.vec = P(MathCommand, function(_, super_) {
- _.ctrlSeq = '\\vec';
- _.htmlTemplate =
- '<span class="mq-non-leaf">'
- + '<span class="mq-vector-prefix">→</span>'
- + '<span class="mq-vector-stem">&0</span>'
- + '</span>'
- ;
- _.textTemplate = ['vec(', ')'];
- });
- var NthRoot =
- LatexCmds.nthroot = P(SquareRoot, function(_, super_) {
- _.htmlTemplate =
- '<sup class="mq-nthroot mq-non-leaf">&0</sup>'
- + '<span class="mq-scaled">'
- + '<span class="mq-sqrt-prefix mq-scaled">√</span>'
- + '<span class="mq-sqrt-stem mq-non-leaf">&1</span>'
- + '</span>'
- ;
- _.textTemplate = ['sqrt[', '](', ')'];
- _.latex = function() {
- return '\\sqrt['+this.ends[L].latex()+']{'+this.ends[R].latex()+'}';
- };
- });
- function DelimsMixin(_, super_) {
- _.jQadd = function() {
- super_.jQadd.apply(this, arguments);
- this.delimjQs = this.jQ.children(':first').add(this.jQ.children(':last'));
- this.contentjQ = this.jQ.children(':eq(1)');
- };
- _.reflow = function() {
- var height = this.contentjQ.outerHeight()
- / parseFloat(this.contentjQ.css('fontSize'));
- scale(this.delimjQs, min(1 + .2*(height - 1), 1.2), 1.2*height);
- };
- }
- // Round/Square/Curly/Angle Brackets (aka Parens/Brackets/Braces)
- // first typed as one-sided bracket with matching "ghost" bracket at
- // far end of current block, until you type an opposing one
- var Bracket = P(P(MathCommand, DelimsMixin), function(_, super_) {
- _.init = function(side, open, close, ctrlSeq, end) {
- super_.init.call(this, '\\left'+ctrlSeq, undefined, [open, close]);
- this.side = side;
- this.sides = {};
- this.sides[L] = { ch: open, ctrlSeq: ctrlSeq };
- this.sides[R] = { ch: close, ctrlSeq: end };
- };
- _.numBlocks = function() { return 1; };
- _.html = function() { // wait until now so that .side may
- this.htmlTemplate = // be set by createLeftOf or parser
- '<span class="mq-non-leaf">'
- + '<span class="mq-scaled mq-paren'+(this.side === R ? ' mq-ghost' : '')+'">'
- + this.sides[L].ch
- + '</span>'
- + '<span class="mq-non-leaf">&0</span>'
- + '<span class="mq-scaled mq-paren'+(this.side === L ? ' mq-ghost' : '')+'">'
- + this.sides[R].ch
- + '</span>'
- + '</span>'
- ;
- return super_.html.call(this);
- };
- _.latex = function() {
- return '\\left'+this.sides[L].ctrlSeq+this.ends[L].latex()+'\\right'+this.sides[R].ctrlSeq;
- };
- _.oppBrack = function(opts, node, expectedSide) {
- // return node iff it's a 1-sided bracket of expected side (if any, may be
- // undefined), and of opposite side from me if I'm not a pipe
- return node instanceof Bracket && node.side && node.side !== -expectedSide
- && (this.sides[this.side].ch === '|' || node.side === -this.side)
- && (!opts.restrictMismatchedBrackets
- || OPP_BRACKS[this.sides[this.side].ch] === node.sides[node.side].ch
- || { '(': ']', '[': ')' }[this.sides[L].ch] === node.sides[R].ch) && node;
- };
- _.closeOpposing = function(brack) {
- brack.side = 0;
- brack.sides[this.side] = this.sides[this.side]; // copy over my info (may be
- brack.delimjQs.eq(this.side === L ? 0 : 1) // mismatched, like [a, b))
- .removeClass('mq-ghost').html(this.sides[this.side].ch);
- };
- _.createLeftOf = function(cursor) {
- if (!this.replacedFragment) { // unless wrapping seln in brackets,
- // check if next to or inside an opposing one-sided bracket
- // (must check both sides 'cos I might be a pipe)
- var opts = cursor.options;
- var brack = this.oppBrack(opts, cursor[L], L)
- || this.oppBrack(opts, cursor[R], R)
- || this.oppBrack(opts, cursor.parent.parent);
- }
- if (brack) {
- var side = this.side = -brack.side; // may be pipe with .side not yet set
- this.closeOpposing(brack);
- if (brack === cursor.parent.parent && cursor[side]) { // move the stuff between
- Fragment(cursor[side], cursor.parent.ends[side], -side) // me and ghost outside
- .disown().withDirAdopt(-side, brack.parent, brack, brack[side])
- .jQ.insDirOf(side, brack.jQ);
- brack.bubble('reflow');
- }
- }
- else {
- brack = this, side = brack.side;
- if (brack.replacedFragment) brack.side = 0; // wrapping seln, don't be one-sided
- else if (cursor[-side]) { // elsewise, auto-expand so ghost is at far end
- brack.replaces(Fragment(cursor[-side], cursor.parent.ends[-side], side));
- cursor[-side] = 0;
- }
- super_.createLeftOf.call(brack, cursor);
- }
- if (side === L) cursor.insAtLeftEnd(brack.ends[L]);
- else cursor.insRightOf(brack);
- };
- _.placeCursor = noop;
- _.unwrap = function() {
- this.ends[L].children().disown().adopt(this.parent, this, this[R])
- .jQ.insertAfter(this.jQ);
- this.remove();
- };
- _.deleteSide = function(side, outward, cursor) {
- var parent = this.parent, sib = this[side], farEnd = parent.ends[side];
- if (side === this.side) { // deleting non-ghost of one-sided bracket, unwrap
- this.unwrap();
- sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
- return;
- }
- var opts = cursor.options, wasSolid = !this.side;
- this.side = -side;
- // if deleting like, outer close-brace of [(1+2)+3} where inner open-paren
- if (this.oppBrack(opts, this.ends[L].ends[this.side], side)) { // is ghost,
- this.closeOpposing(this.ends[L].ends[this.side]); // then become [1+2)+3
- var origEnd = this.ends[L].ends[side];
- this.unwrap();
- if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
- sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
- }
- else { // if deleting like, inner close-brace of ([1+2}+3) where outer
- if (this.oppBrack(opts, this.parent.parent, side)) { // open-paren is
- this.parent.parent.closeOpposing(this); // ghost, then become [1+2+3)
- this.parent.parent.unwrap();
- } // else if deleting outward from a solid pair, unwrap
- else if (outward && wasSolid) {
- this.unwrap();
- sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
- return;
- }
- else { // else deleting just one of a pair of brackets, become one-sided
- this.sides[side] = { ch: OPP_BRACKS[this.sides[this.side].ch],
- ctrlSeq: OPP_BRACKS[this.sides[this.side].ctrlSeq] };
- this.delimjQs.removeClass('mq-ghost')
- .eq(side === L ? 0 : 1).addClass('mq-ghost').html(this.sides[side].ch);
- }
- if (sib) { // auto-expand so ghost is at far end
- var origEnd = this.ends[L].ends[side];
- Fragment(sib, farEnd, -side).disown()
- .withDirAdopt(-side, this.ends[L], origEnd, 0)
- .jQ.insAtDirEnd(side, this.ends[L].jQ.removeClass('mq-empty'));
- if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
- cursor.insDirOf(-side, sib);
- } // didn't auto-expand, cursor goes just outside or just inside parens
- else (outward ? cursor.insDirOf(side, this)
- : cursor.insAtDirEnd(side, this.ends[L]));
- }
- };
- _.deleteTowards = function(dir, cursor) {
- this.deleteSide(-dir, false, cursor);
- };
- _.finalizeTree = function() {
- this.ends[L].deleteOutOf = function(dir, cursor) {
- this.parent.deleteSide(dir, true, cursor);
- };
- // FIXME HACK: after initial creation/insertion, finalizeTree would only be
- // called if the paren is selected and replaced, e.g. by LiveFraction
- this.finalizeTree = this.intentionalBlur = function() {
- this.delimjQs.eq(this.side === L ? 1 : 0).removeClass('mq-ghost');
- this.side = 0;
- };
- };
- _.siblingCreated = function(opts, dir) { // if something typed between ghost and far
- if (dir === -this.side) this.finalizeTree(); // end of its block, solidify
- };
- });
- var OPP_BRACKS = {
- '(': ')',
- ')': '(',
- '[': ']',
- ']': '[',
- '{': '}',
- '}': '{',
- '\\{': '\\}',
- '\\}': '\\{',
- '⟨': '⟩',
- '⟩': '⟨',
- '\\langle ': '\\rangle ',
- '\\rangle ': '\\langle ',
- '|': '|'
- };
- function bindCharBracketPair(open, ctrlSeq) {
- var ctrlSeq = ctrlSeq || open, close = OPP_BRACKS[open], end = OPP_BRACKS[ctrlSeq];
- CharCmds[open] = bind(Bracket, L, open, close, ctrlSeq, end);
- CharCmds[close] = bind(Bracket, R, open, close, ctrlSeq, end);
- }
- bindCharBracketPair('(');
- bindCharBracketPair('[');
- bindCharBracketPair('{', '\\{');
- LatexCmds.langle = bind(Bracket, L, '⟨', '⟩', '\\langle ', '\\rangle ');
- LatexCmds.rangle = bind(Bracket, R, '⟨', '⟩', '\\langle ', '\\rangle ');
- CharCmds['|'] = bind(Bracket, L, '|', '|', '|', '|');
- LatexCmds.left = P(MathCommand, function(_) {
- _.parser = function() {
- var regex = Parser.regex;
- var string = Parser.string;
- var succeed = Parser.succeed;
- var optWhitespace = Parser.optWhitespace;
- return optWhitespace.then(regex(/^(?:[([|]|\\\{)/))
- .then(function(ctrlSeq) { // TODO: \langle, \rangle
- var open = (ctrlSeq.charAt(0) === '\\' ? ctrlSeq.slice(1) : ctrlSeq);
- return latexMathParser.then(function (block) {
- return string('\\right').skip(optWhitespace)
- .then(regex(/^(?:[\])|]|\\\})/)).map(function(end) {
- var close = (end.charAt(0) === '\\' ? end.slice(1) : end);
- var cmd = Bracket(0, open, close, ctrlSeq, end);
- cmd.blocks = [ block ];
- block.adopt(cmd, 0, 0);
- return cmd;
- })
- ;
- });
- })
- ;
- };
- });
- LatexCmds.right = P(MathCommand, function(_) {
- _.parser = function() {
- return Parser.fail('unmatched \\right');
- };
- });
- var Binomial =
- LatexCmds.binom =
- LatexCmds.binomial = P(P(MathCommand, DelimsMixin), function(_, super_) {
- _.ctrlSeq = '\\binom';
- _.htmlTemplate =
- '<span class="mq-non-leaf">'
- + '<span class="mq-paren mq-scaled">(</span>'
- + '<span class="mq-non-leaf">'
- + '<span class="mq-array mq-non-leaf">'
- + '<span>&0</span>'
- + '<span>&1</span>'
- + '</span>'
- + '</span>'
- + '<span class="mq-paren mq-scaled">)</span>'
- + '</span>'
- ;
- _.textTemplate = ['choose(',',',')'];
- });
- var Choose =
- LatexCmds.choose = P(Binomial, function(_) {
- _.createLeftOf = LiveFraction.prototype.createLeftOf;
- });
- LatexCmds.editable = // backcompat with before cfd3620 on #233
- LatexCmds.MathQuillMathField = P(MathCommand, function(_, super_) {
- _.ctrlSeq = '\\MathQuillMathField';
- _.htmlTemplate =
- '<span class="mq-editable-field">'
- + '<span class="mq-root-block">&0</span>'
- + '</span>'
- ;
- _.parser = function() {
- var self = this,
- string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
- return string('[').then(regex(/^[a-z][a-z0-9]*/i)).skip(string(']'))
- .map(function(name) { self.name = name; }).or(succeed())
- .then(super_.parser.call(self));
- };
- _.finalizeTree = function() {
- var ctrlr = Controller(this.ends[L], this.jQ, Options());
- ctrlr.KIND_OF_MQ = 'MathField';
- ctrlr.editable = true;
- ctrlr.createTextarea();
- ctrlr.editablesTextareaEvents();
- ctrlr.cursor.insAtRightEnd(ctrlr.root);
- RootBlockMixin(ctrlr.root);
- };
- _.registerInnerField = function(innerFields, MathField) {
- innerFields.push(innerFields[this.name] = MathField(this.ends[L].controller));
- };
- _.latex = function(){ return this.ends[L].latex(); };
- _.text = function(){ return this.ends[L].text(); };
- });
- // Embed arbitrary things
- // Probably the closest DOM analogue would be an iframe?
- // From MathQuill's perspective, it's a Symbol, it can be
- // anywhere and the cursor can go around it but never in it.
- // Create by calling public API method .dropEmbedded(),
- // or by calling the global public API method .registerEmbed()
- // and rendering LaTeX like \embed{registeredName} (see test).
- var Embed = LatexCmds.embed = P(Symbol, function(_, super_) {
- _.setOptions = function(options) {
- function noop () { return ""; }
- this.text = options.text || noop;
- this.htmlTemplate = options.htmlString || "";
- this.latex = options.latex || noop;
- return this;
- };
- _.parser = function() {
- var self = this;
- string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
- return string('{').then(regex(/^[a-z][a-z0-9]*/i)).skip(string('}'))
- .then(function(name) {
- // the chars allowed in the optional data block are arbitrary other than
- // excluding curly braces and square brackets (which'd be too confusing)
- return string('[').then(regex(/^[-\w\s]*/)).skip(string(']'))
- .or(succeed()).map(function(data) {
- return self.setOptions(EMBEDS[name](data));
- })
- ;
- })
- ;
- };
- });
- var MQ1 = getInterface(1);
- for (var key in MQ1) (function(key, val) {
- if (typeof val === 'function') {
- MathQuill[key] = function() {
- insistOnInterVer();
- return val.apply(this, arguments);
- };
- MathQuill[key].prototype = val.prototype;
- }
- else MathQuill[key] = val;
- }(key, MQ1[key]));
- }());
|