· 5 years ago · May 20, 2020, 05:12 PM
1/**
2 * MathQuill v0.10.1 http://mathquill.com
3 * by Han, Jeanine, and Mary maintainers@mathquill.com
4 *
5 * This Source Code Form is subject to the terms of the
6 * Mozilla Public License, v. 2.0. If a copy of the MPL
7 * was not distributed with this file, You can obtain
8 * one at http://mozilla.org/MPL/2.0/.
9 */
10
11(function() {
12
13var jQuery = window.jQuery,
14 undefined,
15 mqCmdId = 'mathquill-command-id',
16 mqBlockId = 'mathquill-block-id',
17 min = Math.min,
18 max = Math.max;
19
20function noop() {}
21
22/**
23 * A utility higher-order function that makes defining variadic
24 * functions more convenient by letting you essentially define functions
25 * with the last argument as a splat, i.e. the last argument "gathers up"
26 * remaining arguments to the function:
27 * var doStuff = variadic(function(first, rest) { return rest; });
28 * doStuff(1, 2, 3); // => [2, 3]
29 */
30var __slice = [].slice;
31function variadic(fn) {
32 var numFixedArgs = fn.length - 1;
33 return function() {
34 var args = __slice.call(arguments, 0, numFixedArgs);
35 var varArg = __slice.call(arguments, numFixedArgs);
36 return fn.apply(this, args.concat([ varArg ]));
37 };
38}
39
40/**
41 * A utility higher-order function that makes combining object-oriented
42 * programming and functional programming techniques more convenient:
43 * given a method name and any number of arguments to be bound, returns
44 * a function that calls it's first argument's method of that name (if
45 * it exists) with the bound arguments and any additional arguments that
46 * are passed:
47 * var sendMethod = send('method', 1, 2);
48 * var obj = { method: function() { return Array.apply(this, arguments); } };
49 * sendMethod(obj, 3, 4); // => [1, 2, 3, 4]
50 * // or more specifically,
51 * var obj2 = { method: function(one, two, three) { return one*two + three; } };
52 * sendMethod(obj2, 3); // => 5
53 * sendMethod(obj2, 4); // => 6
54 */
55var send = variadic(function(method, args) {
56 return variadic(function(obj, moreArgs) {
57 if (method in obj) return obj[method].apply(obj, args.concat(moreArgs));
58 });
59});
60
61/**
62 * A utility higher-order function that creates "implicit iterators"
63 * from "generators": given a function that takes in a sole argument,
64 * a "yield_" function, that calls "yield_" repeatedly with an object as
65 * a sole argument (presumably objects being iterated over), returns
66 * a function that calls it's first argument on each of those objects
67 * (if the first argument is a function, it is called repeatedly with
68 * each object as the first argument, otherwise it is stringified and
69 * the method of that name is called on each object (if such a method
70 * exists)), passing along all additional arguments:
71 * var a = [
72 * { method: function(list) { list.push(1); } },
73 * { method: function(list) { list.push(2); } },
74 * { method: function(list) { list.push(3); } }
75 * ];
76 * a.each = iterator(function(yield_) {
77 * for (var i in this) yield_(this[i]);
78 * });
79 * var list = [];
80 * a.each('method', list);
81 * list; // => [1, 2, 3]
82 * // Note that the for-in loop will yield 'each', but 'each' maps to
83 * // the function object created by iterator() which does not have a
84 * // .method() method, so that just fails silently.
85 */
86function iterator(generator) {
87 return variadic(function(fn, args) {
88 if (typeof fn !== 'function') fn = send(fn);
89 var yield_ = function(obj) { return fn.apply(obj, [ obj ].concat(args)); };
90 return generator.call(this, yield_);
91 });
92}
93
94/**
95 * sugar to make defining lots of commands easier.
96 * TODO: rethink this.
97 */
98function bind(cons /*, args... */) {
99 var args = __slice.call(arguments, 1);
100 return function() {
101 return cons.apply(this, args);
102 };
103}
104
105/**
106 * a development-only debug method. This definition and all
107 * calls to `pray` will be stripped from the minified
108 * build of mathquill.
109 *
110 * This function must be called by name to be removed
111 * at compile time. Do not define another function
112 * with the same name, and only call this function by
113 * name.
114 */
115function pray(message, cond) {
116 if (!cond) throw new Error('prayer failed: '+message);
117}
118var P = (function(prototype, ownProperty, undefined) {
119 // helper functions that also help minification
120 function isObject(o) { return typeof o === 'object'; }
121 function isFunction(f) { return typeof f === 'function'; }
122
123 // used to extend the prototypes of superclasses (which might not
124 // have `.Bare`s)
125 function SuperclassBare() {}
126
127 return function P(_superclass /* = Object */, definition) {
128 // handle the case where no superclass is given
129 if (definition === undefined) {
130 definition = _superclass;
131 _superclass = Object;
132 }
133
134 // C is the class to be returned.
135 //
136 // It delegates to instantiating an instance of `Bare`, so that it
137 // will always return a new instance regardless of the calling
138 // context.
139 //
140 // TODO: the Chrome inspector shows all created objects as `C`
141 // rather than `Object`. Setting the .name property seems to
142 // have no effect. Is there a way to override this behavior?
143 function C() {
144 var self = new Bare;
145 if (isFunction(self.init)) self.init.apply(self, arguments);
146 return self;
147 }
148
149 // C.Bare is a class with a noop constructor. Its prototype is the
150 // same as C, so that instances of C.Bare are also instances of C.
151 // New objects can be allocated without initialization by calling
152 // `new MyClass.Bare`.
153 function Bare() {}
154 C.Bare = Bare;
155
156 // Set up the prototype of the new class.
157 var _super = SuperclassBare[prototype] = _superclass[prototype];
158 var proto = Bare[prototype] = C[prototype] = C.p = new SuperclassBare;
159
160 // other variables, as a minifier optimization
161 var extensions;
162
163
164 // set the constructor property on the prototype, for convenience
165 proto.constructor = C;
166
167 C.extend = function(def) { return P(C, def); }
168
169 return (C.open = function(def) {
170 extensions = {};
171
172 if (isFunction(def)) {
173 // call the defining function with all the arguments you need
174 // extensions captures the return value.
175 extensions = def.call(C, proto, _super, C, _superclass);
176 }
177 else if (isObject(def)) {
178 // if you passed an object instead, we'll take it
179 extensions = def;
180 }
181
182 // ...and extend it
183 if (isObject(extensions)) {
184 for (var ext in extensions) {
185 if (ownProperty.call(extensions, ext)) {
186 proto[ext] = extensions[ext];
187 }
188 }
189 }
190
191 // if there's no init, we assume we're inheriting a non-pjs class, so
192 // we default to applying the superclass's constructor.
193 if (!isFunction(proto.init)) {
194 proto.init = _superclass;
195 }
196
197 return C;
198 })(definition);
199 }
200
201 // as a minifier optimization, we've closured in a few helper functions
202 // and the string 'prototype' (C[p] is much shorter than C.prototype)
203})('prototype', ({}).hasOwnProperty);
204/*************************************************
205 * Base classes of edit tree-related objects
206 *
207 * Only doing tree node manipulation via these
208 * adopt/ disown methods guarantees well-formedness
209 * of the tree.
210 ************************************************/
211
212// L = 'left'
213// R = 'right'
214//
215// the contract is that they can be used as object properties
216// and (-L) === R, and (-R) === L.
217var L = -1;
218var R = 1;
219
220function prayDirection(dir) {
221 pray('a direction was passed', dir === L || dir === R);
222}
223
224/**
225 * Tiny extension of jQuery adding directionalized DOM manipulation methods.
226 *
227 * Funny how Pjs v3 almost just works with `jQuery.fn.init`.
228 *
229 * jQuery features that don't work on $:
230 * - jQuery.*, like jQuery.ajax, obviously (Pjs doesn't and shouldn't
231 * copy constructor properties)
232 *
233 * - jQuery(function), the shortcut for `jQuery(document).ready(function)`,
234 * because `jQuery.fn.init` is idiosyncratic and Pjs doing, essentially,
235 * `jQuery.fn.init.apply(this, arguments)` isn't quite right, you need:
236 *
237 * _.init = function(s, c) { jQuery.fn.init.call(this, s, c, $(document)); };
238 *
239 * if you actually give a shit (really, don't bother),
240 * see https://github.com/jquery/jquery/blob/1.7.2/src/core.js#L889
241 *
242 * - jQuery(selector), because jQuery translates that to
243 * `jQuery(document).find(selector)`, but Pjs doesn't (should it?) let
244 * you override the result of a constructor call
245 * + note that because of the jQuery(document) shortcut-ness, there's also
246 * the 3rd-argument-needs-to-be-`$(document)` thing above, but the fix
247 * for that (as can be seen above) is really easy. This problem requires
248 * a way more intrusive fix
249 *
250 * And that's it! Everything else just magically works because jQuery internally
251 * uses `this.constructor()` everywhere (hence calling `$`), but never ever does
252 * `this.constructor.find` or anything like that, always doing `jQuery.find`.
253 */
254var $ = P(jQuery, function(_) {
255 _.insDirOf = function(dir, el) {
256 return dir === L ?
257 this.insertBefore(el.first()) : this.insertAfter(el.last());
258 };
259 _.insAtDirEnd = function(dir, el) {
260 return dir === L ? this.prependTo(el) : this.appendTo(el);
261 };
262});
263
264var Point = P(function(_) {
265 _.parent = 0;
266 _[L] = 0;
267 _[R] = 0;
268
269 _.init = function(parent, leftward, rightward) {
270 this.parent = parent;
271 this[L] = leftward;
272 this[R] = rightward;
273 };
274
275 this.copy = function(pt) {
276 return Point(pt.parent, pt[L], pt[R]);
277 };
278});
279
280/**
281 * MathQuill virtual-DOM tree-node abstract base class
282 */
283var Node = P(function(_) {
284 _[L] = 0;
285 _[R] = 0
286 _.parent = 0;
287
288 var id = 0;
289 function uniqueNodeId() { return id += 1; }
290 this.byId = {};
291
292 _.init = function() {
293 this.id = uniqueNodeId();
294 Node.byId[this.id] = this;
295
296 this.ends = {};
297 this.ends[L] = 0;
298 this.ends[R] = 0;
299 };
300
301 _.dispose = function() { delete Node.byId[this.id]; };
302
303 _.toString = function() { return '{{ MathQuill Node #'+this.id+' }}'; };
304
305 _.jQ = $();
306 _.jQadd = function(jQ) { return this.jQ = this.jQ.add(jQ); };
307 _.jQize = function(jQ) {
308 // jQuery-ifies this.html() and links up the .jQ of all corresponding Nodes
309 var jQ = $(jQ || this.html());
310
311 function jQadd(el) {
312 if (el.getAttribute) {
313 var cmdId = el.getAttribute('mathquill-command-id');
314 var blockId = el.getAttribute('mathquill-block-id');
315 if (cmdId) Node.byId[cmdId].jQadd(el);
316 if (blockId) Node.byId[blockId].jQadd(el);
317 }
318 for (el = el.firstChild; el; el = el.nextSibling) {
319 jQadd(el);
320 }
321 }
322
323 for (var i = 0; i < jQ.length; i += 1) jQadd(jQ[i]);
324 return jQ;
325 };
326
327 _.createDir = function(dir, cursor) {
328 prayDirection(dir);
329 var node = this;
330 node.jQize();
331 node.jQ.insDirOf(dir, cursor.jQ);
332 cursor[dir] = node.adopt(cursor.parent, cursor[L], cursor[R]);
333 return node;
334 };
335 _.createLeftOf = function(el) { return this.createDir(L, el); };
336
337 _.selectChildren = function(leftEnd, rightEnd) {
338 return Selection(leftEnd, rightEnd);
339 };
340
341 _.bubble = iterator(function(yield_) {
342 for (var ancestor = this; ancestor; ancestor = ancestor.parent) {
343 var result = yield_(ancestor);
344 if (result === false) break;
345 }
346
347 return this;
348 });
349
350 _.postOrder = iterator(function(yield_) {
351 (function recurse(descendant) {
352 descendant.eachChild(recurse);
353 yield_(descendant);
354 })(this);
355
356 return this;
357 });
358
359 _.isEmpty = function() {
360 return this.ends[L] === 0 && this.ends[R] === 0;
361 };
362
363 _.children = function() {
364 return Fragment(this.ends[L], this.ends[R]);
365 };
366
367 _.eachChild = function() {
368 var children = this.children();
369 children.each.apply(children, arguments);
370 return this;
371 };
372
373 _.foldChildren = function(fold, fn) {
374 return this.children().fold(fold, fn);
375 };
376
377 _.withDirAdopt = function(dir, parent, withDir, oppDir) {
378 Fragment(this, this).withDirAdopt(dir, parent, withDir, oppDir);
379 return this;
380 };
381
382 _.adopt = function(parent, leftward, rightward) {
383 Fragment(this, this).adopt(parent, leftward, rightward);
384 return this;
385 };
386
387 _.disown = function() {
388 Fragment(this, this).disown();
389 return this;
390 };
391
392 _.remove = function() {
393 this.jQ.remove();
394 this.postOrder('dispose');
395 return this.disown();
396 };
397});
398
399function prayWellFormed(parent, leftward, rightward) {
400 pray('a parent is always present', parent);
401 pray('leftward is properly set up', (function() {
402 // either it's empty and `rightward` is the left end child (possibly empty)
403 if (!leftward) return parent.ends[L] === rightward;
404
405 // or it's there and its [R] and .parent are properly set up
406 return leftward[R] === rightward && leftward.parent === parent;
407 })());
408
409 pray('rightward is properly set up', (function() {
410 // either it's empty and `leftward` is the right end child (possibly empty)
411 if (!rightward) return parent.ends[R] === leftward;
412
413 // or it's there and its [L] and .parent are properly set up
414 return rightward[L] === leftward && rightward.parent === parent;
415 })());
416}
417
418
419/**
420 * An entity outside the virtual tree with one-way pointers (so it's only a
421 * "view" of part of the tree, not an actual node/entity in the tree) that
422 * delimits a doubly-linked list of sibling nodes.
423 * It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
424 * classes: like DocumentFragment, its contents must be sibling nodes
425 * (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
426 * but like Range, it has only one-way pointers to its contents, its contents
427 * have no reference to it and in fact may still be in the visible tree (unlike
428 * DocumentFragment, whose contents must be detached from the visible tree
429 * and have their 'parent' pointers set to the DocumentFragment).
430 */
431var Fragment = P(function(_) {
432 _.init = function(withDir, oppDir, dir) {
433 if (dir === undefined) dir = L;
434 prayDirection(dir);
435
436 pray('no half-empty fragments', !withDir === !oppDir);
437
438 this.ends = {};
439
440 if (!withDir) return;
441
442 pray('withDir is passed to Fragment', withDir instanceof Node);
443 pray('oppDir is passed to Fragment', oppDir instanceof Node);
444 pray('withDir and oppDir have the same parent',
445 withDir.parent === oppDir.parent);
446
447 this.ends[dir] = withDir;
448 this.ends[-dir] = oppDir;
449
450 // To build the jquery collection for a fragment, accumulate elements
451 // into an array and then call jQ.add once on the result. jQ.add sorts the
452 // collection according to document order each time it is called, so
453 // building a collection by folding jQ.add directly takes more than
454 // quadratic time in the number of elements.
455 //
456 // https://github.com/jquery/jquery/blob/2.1.4/src/traversing.js#L112
457 var accum = this.fold([], function (accum, el) {
458 accum.push.apply(accum, el.jQ.get());
459 return accum;
460 });
461
462 this.jQ = this.jQ.add(accum);
463 };
464 _.jQ = $();
465
466 // like Cursor::withDirInsertAt(dir, parent, withDir, oppDir)
467 _.withDirAdopt = function(dir, parent, withDir, oppDir) {
468 return (dir === L ? this.adopt(parent, withDir, oppDir)
469 : this.adopt(parent, oppDir, withDir));
470 };
471 _.adopt = function(parent, leftward, rightward) {
472 prayWellFormed(parent, leftward, rightward);
473
474 var self = this;
475 self.disowned = false;
476
477 var leftEnd = self.ends[L];
478 if (!leftEnd) return this;
479
480 var rightEnd = self.ends[R];
481
482 if (leftward) {
483 // NB: this is handled in the ::each() block
484 // leftward[R] = leftEnd
485 } else {
486 parent.ends[L] = leftEnd;
487 }
488
489 if (rightward) {
490 rightward[L] = rightEnd;
491 } else {
492 parent.ends[R] = rightEnd;
493 }
494
495 self.ends[R][R] = rightward;
496
497 self.each(function(el) {
498 el[L] = leftward;
499 el.parent = parent;
500 if (leftward) leftward[R] = el;
501
502 leftward = el;
503 });
504
505 return self;
506 };
507
508 _.disown = function() {
509 var self = this;
510 var leftEnd = self.ends[L];
511
512 // guard for empty and already-disowned fragments
513 if (!leftEnd || self.disowned) return self;
514
515 self.disowned = true;
516
517 var rightEnd = self.ends[R]
518 var parent = leftEnd.parent;
519
520 prayWellFormed(parent, leftEnd[L], leftEnd);
521 prayWellFormed(parent, rightEnd, rightEnd[R]);
522
523 if (leftEnd[L]) {
524 leftEnd[L][R] = rightEnd[R];
525 } else {
526 parent.ends[L] = rightEnd[R];
527 }
528
529 if (rightEnd[R]) {
530 rightEnd[R][L] = leftEnd[L];
531 } else {
532 parent.ends[R] = leftEnd[L];
533 }
534
535 return self;
536 };
537
538 _.remove = function() {
539 this.jQ.remove();
540 this.each('postOrder', 'dispose');
541 return this.disown();
542 };
543
544 _.each = iterator(function(yield_) {
545 var self = this;
546 var el = self.ends[L];
547 if (!el) return self;
548
549 for (; el !== self.ends[R][R]; el = el[R]) {
550 var result = yield_(el);
551 if (result === false) break;
552 }
553
554 return self;
555 });
556
557 _.fold = function(fold, fn) {
558 this.each(function(el) {
559 fold = fn.call(this, fold, el);
560 });
561
562 return fold;
563 };
564});
565
566
567/**
568 * Registry of LaTeX commands and commands created when typing
569 * a single character.
570 *
571 * (Commands are all subclasses of Node.)
572 */
573var LatexCmds = {}, CharCmds = {};
574/********************************************
575 * Cursor and Selection "singleton" classes
576 *******************************************/
577
578/* The main thing that manipulates the Math DOM. Makes sure to manipulate the
579HTML DOM to match. */
580
581/* Sort of singletons, since there should only be one per editable math
582textbox, but any one HTML document can contain many such textboxes, so any one
583JS environment could actually contain many instances. */
584
585//A fake cursor in the fake textbox that the math is rendered in.
586var Cursor = P(Point, function(_) {
587 _.init = function(initParent, options) {
588 this.parent = initParent;
589 this.options = options;
590
591 var jQ = this.jQ = this._jQ = $('<span class="mq-cursor">​</span>');
592 //closured for setInterval
593 this.blink = function(){ jQ.toggleClass('mq-blink'); };
594
595 this.upDownCache = {};
596 };
597
598 _.show = function() {
599 this.jQ = this._jQ.removeClass('mq-blink');
600 if ('intervalId' in this) //already was shown, just restart interval
601 clearInterval(this.intervalId);
602 else { //was hidden and detached, insert this.jQ back into HTML DOM
603 if (this[R]) {
604 if (this.selection && this.selection.ends[L][L] === this[L])
605 this.jQ.insertBefore(this.selection.jQ);
606 else
607 this.jQ.insertBefore(this[R].jQ.first());
608 }
609 else
610 this.jQ.appendTo(this.parent.jQ);
611 this.parent.focus();
612 }
613 this.intervalId = setInterval(this.blink, 500);
614 return this;
615 };
616 _.hide = function() {
617 if ('intervalId' in this)
618 clearInterval(this.intervalId);
619 delete this.intervalId;
620 this.jQ.detach();
621 this.jQ = $();
622 return this;
623 };
624
625 _.withDirInsertAt = function(dir, parent, withDir, oppDir) {
626 var oldParent = this.parent;
627 this.parent = parent;
628 this[dir] = withDir;
629 this[-dir] = oppDir;
630 // by contract, .blur() is called after all has been said and done
631 // and the cursor has actually been moved
632 // FIXME pass cursor to .blur() so text can fix cursor pointers when removing itself
633 if (oldParent !== parent && oldParent.blur) oldParent.blur(this);
634 };
635 _.insDirOf = function(dir, el) {
636 prayDirection(dir);
637 this.jQ.insDirOf(dir, el.jQ);
638 this.withDirInsertAt(dir, el.parent, el[dir], el);
639 this.parent.jQ.addClass('mq-hasCursor');
640 return this;
641 };
642 _.insLeftOf = function(el) { return this.insDirOf(L, el); };
643 _.insRightOf = function(el) { return this.insDirOf(R, el); };
644
645 _.insAtDirEnd = function(dir, el) {
646 prayDirection(dir);
647 this.jQ.insAtDirEnd(dir, el.jQ);
648 this.withDirInsertAt(dir, el, 0, el.ends[dir]);
649 el.focus();
650 return this;
651 };
652 _.insAtLeftEnd = function(el) { return this.insAtDirEnd(L, el); };
653 _.insAtRightEnd = function(el) { return this.insAtDirEnd(R, el); };
654
655 /**
656 * jump up or down from one block Node to another:
657 * - cache the current Point in the node we're jumping from
658 * - check if there's a Point in it cached for the node we're jumping to
659 * + if so put the cursor there,
660 * + if not seek a position in the node that is horizontally closest to
661 * the cursor's current position
662 */
663 _.jumpUpDown = function(from, to) {
664 var self = this;
665 self.upDownCache[from.id] = Point.copy(self);
666 var cached = self.upDownCache[to.id];
667 if (cached) {
668 cached[R] ? self.insLeftOf(cached[R]) : self.insAtRightEnd(cached.parent);
669 }
670 else {
671 var pageX = self.offset().left;
672 to.seek(pageX, self);
673 }
674 };
675 _.offset = function() {
676 //in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset()
677 //returns all 0's on inline elements with negative margin-right (like
678 //the cursor) at the end of their parent, so temporarily remove the
679 //negative margin-right when calling jQuery::offset()
680 //Opera bug DSK-360043
681 //http://bugs.jquery.com/ticket/11523
682 //https://github.com/jquery/jquery/pull/717
683 var self = this, offset = self.jQ.removeClass('mq-cursor').offset();
684 self.jQ.addClass('mq-cursor');
685 return offset;
686 }
687 _.unwrapGramp = function() {
688 var gramp = this.parent.parent;
689 var greatgramp = gramp.parent;
690 var rightward = gramp[R];
691 var cursor = this;
692
693 var leftward = gramp[L];
694 gramp.disown().eachChild(function(uncle) {
695 if (uncle.isEmpty()) return;
696
697 uncle.children()
698 .adopt(greatgramp, leftward, rightward)
699 .each(function(cousin) {
700 cousin.jQ.insertBefore(gramp.jQ.first());
701 })
702 ;
703
704 leftward = uncle.ends[R];
705 });
706
707 if (!this[R]) { //then find something to be rightward to insLeftOf
708 if (this[L])
709 this[R] = this[L][R];
710 else {
711 while (!this[R]) {
712 this.parent = this.parent[R];
713 if (this.parent)
714 this[R] = this.parent.ends[L];
715 else {
716 this[R] = gramp[R];
717 this.parent = greatgramp;
718 break;
719 }
720 }
721 }
722 }
723 if (this[R])
724 this.insLeftOf(this[R]);
725 else
726 this.insAtRightEnd(greatgramp);
727
728 gramp.jQ.remove();
729
730 if (gramp[L].siblingDeleted) gramp[L].siblingDeleted(cursor.options, R);
731 if (gramp[R].siblingDeleted) gramp[R].siblingDeleted(cursor.options, L);
732 };
733 _.startSelection = function() {
734 var anticursor = this.anticursor = Point.copy(this);
735 var ancestors = anticursor.ancestors = {}; // a map from each ancestor of
736 // the anticursor, to its child that is also an ancestor; in other words,
737 // the anticursor's ancestor chain in reverse order
738 for (var ancestor = anticursor; ancestor.parent; ancestor = ancestor.parent) {
739 ancestors[ancestor.parent.id] = ancestor;
740 }
741 };
742 _.endSelection = function() {
743 delete this.anticursor;
744 };
745 _.select = function() {
746 var anticursor = this.anticursor;
747 if (this[L] === anticursor[L] && this.parent === anticursor.parent) return false;
748
749 // Find the lowest common ancestor (`lca`), and the ancestor of the cursor
750 // whose parent is the LCA (which'll be an end of the selection fragment).
751 for (var ancestor = this; ancestor.parent; ancestor = ancestor.parent) {
752 if (ancestor.parent.id in anticursor.ancestors) {
753 var lca = ancestor.parent;
754 break;
755 }
756 }
757 pray('cursor and anticursor in the same tree', lca);
758 // The cursor and the anticursor should be in the same tree, because the
759 // mousemove handler attached to the document, unlike the one attached to
760 // the root HTML DOM element, doesn't try to get the math tree node of the
761 // mousemove target, and Cursor::seek() based solely on coordinates stays
762 // within the tree of `this` cursor's root.
763
764 // The other end of the selection fragment, the ancestor of the anticursor
765 // whose parent is the LCA.
766 var antiAncestor = anticursor.ancestors[lca.id];
767
768 // Now we have two either Nodes or Points, guaranteed to have a common
769 // parent and guaranteed that if both are Points, they are not the same,
770 // and we have to figure out which is the left end and which the right end
771 // of the selection.
772 var leftEnd, rightEnd, dir = R;
773
774 // This is an extremely subtle algorithm.
775 // As a special case, `ancestor` could be a Point and `antiAncestor` a Node
776 // immediately to `ancestor`'s left.
777 // In all other cases,
778 // - both Nodes
779 // - `ancestor` a Point and `antiAncestor` a Node
780 // - `ancestor` a Node and `antiAncestor` a Point
781 // `antiAncestor[R] === rightward[R]` for some `rightward` that is
782 // `ancestor` or to its right, if and only if `antiAncestor` is to
783 // the right of `ancestor`.
784 if (ancestor[L] !== antiAncestor) {
785 for (var rightward = ancestor; rightward; rightward = rightward[R]) {
786 if (rightward[R] === antiAncestor[R]) {
787 dir = L;
788 leftEnd = ancestor;
789 rightEnd = antiAncestor;
790 break;
791 }
792 }
793 }
794 if (dir === R) {
795 leftEnd = antiAncestor;
796 rightEnd = ancestor;
797 }
798
799 // only want to select Nodes up to Points, can't select Points themselves
800 if (leftEnd instanceof Point) leftEnd = leftEnd[R];
801 if (rightEnd instanceof Point) rightEnd = rightEnd[L];
802
803 this.hide().selection = lca.selectChildren(leftEnd, rightEnd);
804 this.insDirOf(dir, this.selection.ends[dir]);
805 this.selectionChanged();
806 return true;
807 };
808
809 _.clearSelection = function() {
810 if (this.selection) {
811 this.selection.clear();
812 delete this.selection;
813 this.selectionChanged();
814 }
815 return this;
816 };
817 _.deleteSelection = function() {
818 if (!this.selection) return;
819
820 this[L] = this.selection.ends[L][L];
821 this[R] = this.selection.ends[R][R];
822 this.selection.remove();
823 this.selectionChanged();
824 delete this.selection;
825 };
826 _.replaceSelection = function() {
827 var seln = this.selection;
828 if (seln) {
829 this[L] = seln.ends[L][L];
830 this[R] = seln.ends[R][R];
831 delete this.selection;
832 }
833 return seln;
834 };
835});
836
837var Selection = P(Fragment, function(_, super_) {
838 _.init = function() {
839 super_.init.apply(this, arguments);
840 this.jQ = this.jQ.wrapAll('<span class="mq-selection"></span>').parent();
841 //can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it
842 };
843 _.adopt = function() {
844 this.jQ.replaceWith(this.jQ = this.jQ.children());
845 return super_.adopt.apply(this, arguments);
846 };
847 _.clear = function() {
848 // using the browser's native .childNodes property so that we
849 // don't discard text nodes.
850 this.jQ.replaceWith(this.jQ[0].childNodes);
851 return this;
852 };
853 _.join = function(methodName) {
854 return this.fold('', function(fold, child) {
855 return fold + child[methodName]();
856 });
857 };
858});
859/*********************************************
860 * Controller for a MathQuill instance,
861 * on which services are registered with
862 *
863 * Controller.open(function(_) { ... });
864 *
865 ********************************************/
866
867var Controller = P(function(_) {
868 _.init = function(root, container, options) {
869 this.id = root.id;
870 this.data = {};
871
872 this.root = root;
873 this.container = container;
874 this.options = options;
875
876 root.controller = this;
877
878 this.cursor = root.cursor = Cursor(root, options);
879 // TODO: stop depending on root.cursor, and rm it
880 };
881
882 _.handle = function(name, dir) {
883 var handlers = this.options.handlers;
884 if (handlers && handlers.fns[name]) {
885 var mq = handlers.APIClasses[this.KIND_OF_MQ](this);
886 if (dir === L || dir === R) handlers.fns[name](dir, mq);
887 else handlers.fns[name](mq);
888 }
889 };
890
891 var notifyees = [];
892 this.onNotify = function(f) { notifyees.push(f); };
893 _.notify = function() {
894 for (var i = 0; i < notifyees.length; i += 1) {
895 notifyees[i].apply(this.cursor, arguments);
896 }
897 return this;
898 };
899});
900/*********************************************************
901 * The publicly exposed MathQuill API.
902 ********************************************************/
903
904var API = {}, Options = P(), optionProcessors = {}, Progenote = P(), EMBEDS = {};
905
906/**
907 * Interface Versioning (#459, #495) to allow us to virtually guarantee
908 * backcompat. v0.10.x introduces it, so for now, don't completely break the
909 * API for people who don't know about it, just complain with console.warn().
910 *
911 * The methods are shimmed in outro.js so that MQ.MathField.prototype etc can
912 * be accessed.
913 */
914function insistOnInterVer() {
915 if (window.console) console.warn(
916 'You are using the MathQuill API without specifying an interface version, ' +
917 'which will fail in v1.0.0. Easiest fix is to do the following before ' +
918 'doing anything else:\n' +
919 '\n' +
920 ' MathQuill = MathQuill.getInterface(1);\n' +
921 ' // now MathQuill.MathField() works like it used to\n' +
922 '\n' +
923 'See also the "`dev` branch (2014\u20132015) \u2192 v0.10.0 Migration Guide" at\n' +
924 ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
925 );
926}
927// globally exported API object
928function MathQuill(el) {
929 insistOnInterVer();
930 return MQ1(el);
931};
932MathQuill.prototype = Progenote.p;
933MathQuill.interfaceVersion = function(v) {
934 // shim for #459-era interface versioning (ended with #495)
935 if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v;
936 insistOnInterVer = function() {
937 if (window.console) console.warn(
938 'You called MathQuill.interfaceVersion(1); to specify the interface ' +
939 'version, which will fail in v1.0.0. You can fix this easily by doing ' +
940 'this before doing anything else:\n' +
941 '\n' +
942 ' MathQuill = MathQuill.getInterface(1);\n' +
943 ' // now MathQuill.MathField() works like it used to\n' +
944 '\n' +
945 'See also the "`dev` branch (2014\u20132015) \u2192 v0.10.0 Migration Guide" at\n' +
946 ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide'
947 );
948 };
949 insistOnInterVer();
950 return MathQuill;
951};
952MathQuill.getInterface = getInterface;
953
954var MIN = getInterface.MIN = 1, MAX = getInterface.MAX = 2;
955function getInterface(v) {
956 if (!(MIN <= v && v <= MAX)) throw 'Only interface versions between ' +
957 MIN + ' and ' + MAX + ' supported. You specified: ' + v;
958
959 /**
960 * Function that takes an HTML element and, if it's the root HTML element of a
961 * static math or math or text field, returns an API object for it (else, null).
962 *
963 * var mathfield = MQ.MathField(mathFieldSpan);
964 * assert(MQ(mathFieldSpan).id === mathfield.id);
965 * assert(MQ(mathFieldSpan).id === MQ(mathFieldSpan).id);
966 *
967 */
968 function MQ(el) {
969 if (!el || !el.nodeType) return null; // check that `el` is a HTML element, using the
970 // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92
971 var blockId = $(el).children('.mq-root-block').attr(mqBlockId);
972 var ctrlr = blockId && Node.byId[blockId].controller;
973 return ctrlr ? APIClasses[ctrlr.KIND_OF_MQ](ctrlr) : null;
974 };
975 var APIClasses = {};
976
977 MQ.L = L;
978 MQ.R = R;
979
980 function config(currentOptions, newOptions) {
981 if (newOptions && newOptions.handlers) {
982 newOptions.handlers = { fns: newOptions.handlers, APIClasses: APIClasses };
983 }
984 for (var name in newOptions) if (newOptions.hasOwnProperty(name)) {
985 var value = newOptions[name], processor = optionProcessors[name];
986 currentOptions[name] = (processor ? processor(value) : value);
987 }
988 }
989 MQ.config = function(opts) { config(Options.p, opts); return this; };
990 MQ.registerEmbed = function(name, options) {
991 if (!/^[a-z][a-z0-9]*$/i.test(name)) {
992 throw 'Embed name must start with letter and be only letters and digits';
993 }
994 EMBEDS[name] = options;
995 };
996
997 var AbstractMathQuill = APIClasses.AbstractMathQuill = P(Progenote, function(_) {
998 _.init = function(ctrlr) {
999 this.__controller = ctrlr;
1000 this.__options = ctrlr.options;
1001 this.id = ctrlr.id;
1002 this.data = ctrlr.data;
1003 };
1004 _.__mathquillify = function(classNames) {
1005 var ctrlr = this.__controller, root = ctrlr.root, el = ctrlr.container;
1006 ctrlr.createTextarea();
1007
1008 var contents = el.addClass(classNames).contents().detach();
1009 root.jQ =
1010 $('<span class="mq-root-block"/>').attr(mqBlockId, root.id).appendTo(el);
1011 this.latex(contents.text());
1012
1013 this.revert = function() {
1014 return el.empty().unbind('.mathquill')
1015 .removeClass('mq-editable-field mq-math-mode mq-text-mode')
1016 .append(contents);
1017 };
1018 };
1019 _.config = function(opts) { config(this.__options, opts); return this; };
1020 _.el = function() { return this.__controller.container[0]; };
1021 _.text = function() { return this.__controller.exportText(); };
1022 _.latex = function(latex) {
1023 if (arguments.length > 0) {
1024 this.__controller.renderLatexMath(latex);
1025 if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
1026 return this;
1027 }
1028 return this.__controller.exportLatex();
1029 };
1030 _.html = function() {
1031 return this.__controller.root.jQ.html()
1032 .replace(/ mathquill-(?:command|block)-id="?\d+"?/g, '')
1033 .replace(/<span class="?mq-cursor( mq-blink)?"?>.?<\/span>/i, '')
1034 .replace(/ mq-hasCursor|mq-hasCursor ?/, '')
1035 .replace(/ class=(""|(?= |>))/g, '');
1036 };
1037 _.reflow = function() {
1038 this.__controller.root.postOrder('reflow');
1039 return this;
1040 };
1041 });
1042 MQ.prototype = AbstractMathQuill.prototype;
1043
1044 APIClasses.EditableField = P(AbstractMathQuill, function(_, super_) {
1045 _.__mathquillify = function() {
1046 super_.__mathquillify.apply(this, arguments);
1047 this.__controller.editable = true;
1048 this.__controller.delegateMouseEvents();
1049 this.__controller.editablesTextareaEvents();
1050 return this;
1051 };
1052 _.focus = function() { this.__controller.textarea.focus(); return this; };
1053 _.blur = function() { this.__controller.textarea.blur(); return this; };
1054 _.write = function(latex) {
1055 this.__controller.writeLatex(latex);
1056 this.__controller.scrollHoriz();
1057 if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
1058 return this;
1059 };
1060 _.cmd = function(cmd) {
1061 var ctrlr = this.__controller.notify(), cursor = ctrlr.cursor;
1062 if (/^\\[a-z]+$/i.test(cmd)) {
1063 cmd = cmd.slice(1);
1064 var klass = LatexCmds[cmd];
1065 if (klass) {
1066 cmd = klass(cmd);
1067 if (cursor.selection) cmd.replaces(cursor.replaceSelection());
1068 cmd.createLeftOf(cursor.show());
1069 this.__controller.scrollHoriz();
1070 }
1071 else /* TODO: API needs better error reporting */;
1072 }
1073 else cursor.parent.write(cursor, cmd);
1074 if (ctrlr.blurred) cursor.hide().parent.blur();
1075 return this;
1076 };
1077 _.select = function() {
1078 var ctrlr = this.__controller;
1079 ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
1080 while (ctrlr.cursor[L]) ctrlr.selectLeft();
1081 return this;
1082 };
1083 _.clearSelection = function() {
1084 this.__controller.cursor.clearSelection();
1085 return this;
1086 };
1087
1088 _.moveToDirEnd = function(dir) {
1089 this.__controller.notify('move').cursor.insAtDirEnd(dir, this.__controller.root);
1090 return this;
1091 };
1092 _.moveToLeftEnd = function() { return this.moveToDirEnd(L); };
1093 _.moveToRightEnd = function() { return this.moveToDirEnd(R); };
1094
1095 _.keystroke = function(keys) {
1096 var keys = keys.replace(/^\s+|\s+$/g, '').split(/\s+/);
1097 for (var i = 0; i < keys.length; i += 1) {
1098 this.__controller.keystroke(keys[i], { preventDefault: noop });
1099 }
1100 return this;
1101 };
1102 _.typedText = function(text) {
1103 for (var i = 0; i < text.length; i += 1) this.__controller.typedText(text.charAt(i));
1104 return this;
1105 };
1106 _.dropEmbedded = function(pageX, pageY, options) {
1107 var clientX = pageX - $(window).scrollLeft();
1108 var clientY = pageY - $(window).scrollTop();
1109
1110 var el = document.elementFromPoint(clientX, clientY);
1111 this.__controller.seek($(el), pageX, pageY);
1112 var cmd = Embed().setOptions(options);
1113 cmd.createLeftOf(this.__controller.cursor);
1114 };
1115 _.clickAt = function(clientX, clientY, target) {
1116 target = target || document.elementFromPoint(clientX, clientY);
1117
1118 var ctrlr = this.__controller, root = ctrlr.root;
1119 if (!jQuery.contains(root.jQ[0], target)) target = root.jQ[0];
1120 ctrlr.seek($(target), clientX + pageXOffset, clientY + pageYOffset);
1121 if (ctrlr.blurred) this.focus();
1122 return this;
1123 };
1124 _.ignoreNextMousedown = function(fn) {
1125 this.__controller.cursor.options.ignoreNextMousedown = fn;
1126 return this;
1127 };
1128 });
1129 MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; };
1130 MQ.EditableField.prototype = APIClasses.EditableField.prototype;
1131
1132 /**
1133 * Export the API functions that MathQuill-ify an HTML element into API objects
1134 * of each class. If the element had already been MathQuill-ified but into a
1135 * different kind (or it's not an HTML element), return null.
1136 */
1137 for (var kind in API) (function(kind, defAPIClass) {
1138 var APIClass = APIClasses[kind] = defAPIClass(APIClasses);
1139 MQ[kind] = function(el, opts) {
1140 var mq = MQ(el);
1141 if (mq instanceof APIClass || !el || !el.nodeType) return mq;
1142 var ctrlr = Controller(APIClass.RootBlock(), $(el), Options());
1143 ctrlr.KIND_OF_MQ = kind;
1144 return APIClass(ctrlr).__mathquillify(opts, v);
1145 };
1146 MQ[kind].prototype = APIClass.prototype;
1147 }(kind, API[kind]));
1148
1149 return MQ;
1150}
1151
1152MathQuill.noConflict = function() {
1153 window.MathQuill = origMathQuill;
1154 return MathQuill;
1155};
1156var origMathQuill = window.MathQuill;
1157window.MathQuill = MathQuill;
1158
1159function RootBlockMixin(_) {
1160 var names = 'moveOutOf deleteOutOf selectOutOf upOutOf downOutOf'.split(' ');
1161 for (var i = 0; i < names.length; i += 1) (function(name) {
1162 _[name] = function(dir) { this.controller.handle(name, dir); };
1163 }(names[i]));
1164 _.reflow = function() {
1165 this.controller.handle('reflow');
1166 this.controller.handle('edited');
1167 this.controller.handle('edit');
1168 };
1169}
1170var Parser = P(function(_, super_, Parser) {
1171 // The Parser object is a wrapper for a parser function.
1172 // Externally, you use one to parse a string by calling
1173 // var result = SomeParser.parse('Me Me Me! Parse Me!');
1174 // You should never call the constructor, rather you should
1175 // construct your Parser from the base parsers and the
1176 // parser combinator methods.
1177
1178 function parseError(stream, message) {
1179 if (stream) {
1180 stream = "'"+stream+"'";
1181 }
1182 else {
1183 stream = 'EOF';
1184 }
1185
1186 throw 'Parse Error: '+message+' at '+stream;
1187 }
1188
1189 _.init = function(body) { this._ = body; };
1190
1191 _.parse = function(stream) {
1192 return this.skip(eof)._(''+stream, success, parseError);
1193
1194 function success(stream, result) { return result; }
1195 };
1196
1197 // -*- primitive combinators -*- //
1198 _.or = function(alternative) {
1199 pray('or is passed a parser', alternative instanceof Parser);
1200
1201 var self = this;
1202
1203 return Parser(function(stream, onSuccess, onFailure) {
1204 return self._(stream, onSuccess, failure);
1205
1206 function failure(newStream) {
1207 return alternative._(stream, onSuccess, onFailure);
1208 }
1209 });
1210 };
1211
1212 _.then = function(next) {
1213 var self = this;
1214
1215 return Parser(function(stream, onSuccess, onFailure) {
1216 return self._(stream, success, onFailure);
1217
1218 function success(newStream, result) {
1219 var nextParser = (next instanceof Parser ? next : next(result));
1220 pray('a parser is returned', nextParser instanceof Parser);
1221 return nextParser._(newStream, onSuccess, onFailure);
1222 }
1223 });
1224 };
1225
1226 // -*- optimized iterative combinators -*- //
1227 _.many = function() {
1228 var self = this;
1229
1230 return Parser(function(stream, onSuccess, onFailure) {
1231 var xs = [];
1232 while (self._(stream, success, failure));
1233 return onSuccess(stream, xs);
1234
1235 function success(newStream, x) {
1236 stream = newStream;
1237 xs.push(x);
1238 return true;
1239 }
1240
1241 function failure() {
1242 return false;
1243 }
1244 });
1245 };
1246
1247 _.times = function(min, max) {
1248 if (arguments.length < 2) max = min;
1249 var self = this;
1250
1251 return Parser(function(stream, onSuccess, onFailure) {
1252 var xs = [];
1253 var result = true;
1254 var failure;
1255
1256 for (var i = 0; i < min; i += 1) {
1257 result = self._(stream, success, firstFailure);
1258 if (!result) return onFailure(stream, failure);
1259 }
1260
1261 for (; i < max && result; i += 1) {
1262 result = self._(stream, success, secondFailure);
1263 }
1264
1265 return onSuccess(stream, xs);
1266
1267 function success(newStream, x) {
1268 xs.push(x);
1269 stream = newStream;
1270 return true;
1271 }
1272
1273 function firstFailure(newStream, msg) {
1274 failure = msg;
1275 stream = newStream;
1276 return false;
1277 }
1278
1279 function secondFailure(newStream, msg) {
1280 return false;
1281 }
1282 });
1283 };
1284
1285 // -*- higher-level combinators -*- //
1286 _.result = function(res) { return this.then(succeed(res)); };
1287 _.atMost = function(n) { return this.times(0, n); };
1288 _.atLeast = function(n) {
1289 var self = this;
1290 return self.times(n).then(function(start) {
1291 return self.many().map(function(end) {
1292 return start.concat(end);
1293 });
1294 });
1295 };
1296
1297 _.map = function(fn) {
1298 return this.then(function(result) { return succeed(fn(result)); });
1299 };
1300
1301 _.skip = function(two) {
1302 return this.then(function(result) { return two.result(result); });
1303 };
1304
1305 // -*- primitive parsers -*- //
1306 var string = this.string = function(str) {
1307 var len = str.length;
1308 var expected = "expected '"+str+"'";
1309
1310 return Parser(function(stream, onSuccess, onFailure) {
1311 var head = stream.slice(0, len);
1312
1313 if (head === str) {
1314 return onSuccess(stream.slice(len), head);
1315 }
1316 else {
1317 return onFailure(stream, expected);
1318 }
1319 });
1320 };
1321
1322 var regex = this.regex = function(re) {
1323 pray('regexp parser is anchored', re.toString().charAt(1) === '^');
1324
1325 var expected = 'expected '+re;
1326
1327 return Parser(function(stream, onSuccess, onFailure) {
1328 var match = re.exec(stream);
1329
1330 if (match) {
1331 var result = match[0];
1332 return onSuccess(stream.slice(result.length), result);
1333 }
1334 else {
1335 return onFailure(stream, expected);
1336 }
1337 });
1338 };
1339
1340 var succeed = Parser.succeed = function(result) {
1341 return Parser(function(stream, onSuccess) {
1342 return onSuccess(stream, result);
1343 });
1344 };
1345
1346 var fail = Parser.fail = function(msg) {
1347 return Parser(function(stream, _, onFailure) {
1348 return onFailure(stream, msg);
1349 });
1350 };
1351
1352 var letter = Parser.letter = regex(/^[a-z]/i);
1353 var letters = Parser.letters = regex(/^[a-z]*/i);
1354 var digit = Parser.digit = regex(/^[0-9]/);
1355 var digits = Parser.digits = regex(/^[0-9]*/);
1356 var whitespace = Parser.whitespace = regex(/^\s+/);
1357 var optWhitespace = Parser.optWhitespace = regex(/^\s*/);
1358
1359 var any = Parser.any = Parser(function(stream, onSuccess, onFailure) {
1360 if (!stream) return onFailure(stream, 'expected any character');
1361
1362 return onSuccess(stream.slice(1), stream.charAt(0));
1363 });
1364
1365 var all = Parser.all = Parser(function(stream, onSuccess, onFailure) {
1366 return onSuccess('', stream);
1367 });
1368
1369 var eof = Parser.eof = Parser(function(stream, onSuccess, onFailure) {
1370 if (stream) return onFailure(stream, 'expected EOF');
1371
1372 return onSuccess(stream, stream);
1373 });
1374});
1375/*************************************************
1376 * Sane Keyboard Events Shim
1377 *
1378 * An abstraction layer wrapping the textarea in
1379 * an object with methods to manipulate and listen
1380 * to events on, that hides all the nasty cross-
1381 * browser incompatibilities behind a uniform API.
1382 *
1383 * Design goal: This is a *HARD* internal
1384 * abstraction barrier. Cross-browser
1385 * inconsistencies are not allowed to leak through
1386 * and be dealt with by event handlers. All future
1387 * cross-browser issues that arise must be dealt
1388 * with here, and if necessary, the API updated.
1389 *
1390 * Organization:
1391 * - key values map and stringify()
1392 * - saneKeyboardEvents()
1393 * + defer() and flush()
1394 * + event handler logic
1395 * + attach event handlers and export methods
1396 ************************************************/
1397
1398var saneKeyboardEvents = (function() {
1399 // The following [key values][1] map was compiled from the
1400 // [DOM3 Events appendix section on key codes][2] and
1401 // [a widely cited report on cross-browser tests of key codes][3],
1402 // except for 10: 'Enter', which I've empirically observed in Safari on iOS
1403 // and doesn't appear to conflict with any other known key codes.
1404 //
1405 // [1]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#keys-keyvalues
1406 // [2]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes
1407 // [3]: http://unixpapa.com/js/key.html
1408 var KEY_VALUES = {
1409 8: 'Backspace',
1410 9: 'Tab',
1411
1412 10: 'Enter', // for Safari on iOS
1413
1414 13: 'Enter',
1415
1416 16: 'Shift',
1417 17: 'Control',
1418 18: 'Alt',
1419 20: 'CapsLock',
1420
1421 27: 'Esc',
1422
1423 32: 'Spacebar',
1424
1425 33: 'PageUp',
1426 34: 'PageDown',
1427 35: 'End',
1428 36: 'Home',
1429
1430 37: 'Left',
1431 38: 'Up',
1432 39: 'Right',
1433 40: 'Down',
1434
1435 45: 'Insert',
1436
1437 46: 'Del',
1438
1439 144: 'NumLock'
1440 };
1441
1442 // To the extent possible, create a normalized string representation
1443 // of the key combo (i.e., key code and modifier keys).
1444 function stringify(evt) {
1445 var which = evt.which || evt.keyCode;
1446 var keyVal = KEY_VALUES[which];
1447 var key;
1448 var modifiers = [];
1449
1450 if (evt.ctrlKey) modifiers.push('Ctrl');
1451 if (evt.originalEvent && evt.originalEvent.metaKey) modifiers.push('Meta');
1452 if (evt.altKey) modifiers.push('Alt');
1453 if (evt.shiftKey) modifiers.push('Shift');
1454
1455 key = keyVal || String.fromCharCode(which);
1456
1457 if (!modifiers.length && !keyVal) return key;
1458
1459 modifiers.push(key);
1460 return modifiers.join('-');
1461 }
1462
1463 // create a keyboard events shim that calls callbacks at useful times
1464 // and exports useful public methods
1465 return function saneKeyboardEvents(el, handlers) {
1466 var keydown = null;
1467 var keypress = null;
1468
1469 var textarea = jQuery(el);
1470 var target = jQuery(handlers.container || textarea);
1471
1472 // checkTextareaFor() is called after keypress or paste events to
1473 // say "Hey, I think something was just typed" or "pasted" (resp.),
1474 // so that at all subsequent opportune times (next event or timeout),
1475 // will check for expected typed or pasted text.
1476 // Need to check repeatedly because #135: in Safari 5.1 (at least),
1477 // after selecting something and then typing, the textarea is
1478 // incorrectly reported as selected during the input event (but not
1479 // subsequently).
1480 var checkTextarea = noop, timeoutId;
1481 function checkTextareaFor(checker) {
1482 checkTextarea = checker;
1483 clearTimeout(timeoutId);
1484 timeoutId = setTimeout(checker);
1485 }
1486 target.bind('keydown keypress input keyup focusout paste', function(e) { checkTextarea(e); });
1487
1488
1489 // -*- public methods -*- //
1490 function select(text) {
1491 // check textarea at least once/one last time before munging (so
1492 // no race condition if selection happens after keypress/paste but
1493 // before checkTextarea), then never again ('cos it's been munged)
1494 checkTextarea();
1495 checkTextarea = noop;
1496 clearTimeout(timeoutId);
1497
1498 textarea.val(text);
1499 if (text && textarea[0].select) textarea[0].select();
1500 shouldBeSelected = !!text;
1501 }
1502 var shouldBeSelected = false;
1503
1504 // -*- helper subroutines -*- //
1505
1506 // Determine whether there's a selection in the textarea.
1507 // This will always return false in IE < 9, which don't support
1508 // HTMLTextareaElement::selection{Start,End}.
1509 function hasSelection() {
1510 var dom = textarea[0];
1511
1512 if (!('selectionStart' in dom)) return false;
1513 return dom.selectionStart !== dom.selectionEnd;
1514 }
1515
1516 function handleKey() {
1517 handlers.keystroke(stringify(keydown), keydown);
1518 }
1519
1520 // -*- event handlers -*- //
1521 function onKeydown(e) {
1522 keydown = e;
1523 keypress = null;
1524
1525 if (shouldBeSelected) checkTextareaFor(function(e) {
1526 if (!(e && e.type === 'focusout') && textarea[0].select) {
1527 textarea[0].select(); // re-select textarea in case it's an unrecognized
1528 }
1529 checkTextarea = noop; // key that clears the selection, then never
1530 clearTimeout(timeoutId); // again, 'cos next thing might be blur
1531 });
1532
1533 handleKey();
1534 }
1535
1536 function onKeypress(e) {
1537 // call the key handler for repeated keypresses.
1538 // This excludes keypresses that happen directly
1539 // after keydown. In that case, there will be
1540 // no previous keypress, so we skip it here
1541 if (keydown && keypress) handleKey();
1542
1543 keypress = e;
1544
1545 checkTextareaFor(typedText);
1546 }
1547 function typedText() {
1548 // If there is a selection, the contents of the textarea couldn't
1549 // possibly have just been typed in.
1550 // This happens in browsers like Firefox and Opera that fire
1551 // keypress for keystrokes that are not text entry and leave the
1552 // selection in the textarea alone, such as Ctrl-C.
1553 // Note: we assume that browsers that don't support hasSelection()
1554 // also never fire keypress on keystrokes that are not text entry.
1555 // This seems reasonably safe because:
1556 // - all modern browsers including IE 9+ support hasSelection(),
1557 // making it extremely unlikely any browser besides IE < 9 won't
1558 // - as far as we know IE < 9 never fires keypress on keystrokes
1559 // that aren't text entry, which is only as reliable as our
1560 // tests are comprehensive, but the IE < 9 way to do
1561 // hasSelection() is poorly documented and is also only as
1562 // reliable as our tests are comprehensive
1563 // If anything like #40 or #71 is reported in IE < 9, see
1564 // b1318e5349160b665003e36d4eedd64101ceacd8
1565 if (hasSelection()) return;
1566
1567 var text = textarea.val();
1568 if (text.length === 1) {
1569 textarea.val('');
1570 handlers.typedText(text);
1571 } // in Firefox, keys that don't type text, just clear seln, fire keypress
1572 // https://github.com/mathquill/mathquill/issues/293#issuecomment-40997668
1573 else if (text && textarea[0].select) textarea[0].select(); // re-select if that's why we're here
1574 }
1575
1576 function onBlur() { keydown = keypress = null; }
1577
1578 function onPaste(e) {
1579 // browsers are dumb.
1580 //
1581 // In Linux, middle-click pasting causes onPaste to be called,
1582 // when the textarea is not necessarily focused. We focus it
1583 // here to ensure that the pasted text actually ends up in the
1584 // textarea.
1585 //
1586 // It's pretty nifty that by changing focus in this handler,
1587 // we can change the target of the default action. (This works
1588 // on keydown too, FWIW).
1589 //
1590 // And by nifty, we mean dumb (but useful sometimes).
1591 textarea.focus();
1592
1593 checkTextareaFor(pastedText);
1594 }
1595 function pastedText() {
1596 var text = textarea.val();
1597 textarea.val('');
1598 if (text) handlers.paste(text);
1599 }
1600
1601 // -*- attach event handlers -*- //
1602 target.bind({
1603 keydown: onKeydown,
1604 keypress: onKeypress,
1605 focusout: onBlur,
1606 paste: onPaste
1607 });
1608
1609 // -*- export public methods -*- //
1610 return {
1611 select: select
1612 };
1613 };
1614}());
1615/***********************************************
1616 * Export math in a human-readable text format
1617 * As you can see, only half-baked so far.
1618 **********************************************/
1619
1620Controller.open(function(_, super_) {
1621 _.exportText = function() {
1622 return this.root.foldChildren('', function(text, child) {
1623 return text + child.text();
1624 });
1625 };
1626});
1627Controller.open(function(_) {
1628 _.focusBlurEvents = function() {
1629 var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor;
1630 var blurTimeout;
1631 ctrlr.textarea.focus(function() {
1632 ctrlr.blurred = false;
1633 clearTimeout(blurTimeout);
1634 ctrlr.container.addClass('mq-focused');
1635 if (!cursor.parent)
1636 cursor.insAtRightEnd(root);
1637 if (cursor.selection) {
1638 cursor.selection.jQ.removeClass('mq-blur');
1639 ctrlr.selectionChanged(); //re-select textarea contents after tabbing away and back
1640 }
1641 else
1642 cursor.show();
1643 }).blur(function() {
1644 ctrlr.blurred = true;
1645 blurTimeout = setTimeout(function() { // wait for blur on window; if
1646 root.postOrder('intentionalBlur'); // none, intentional blur: #264
1647 cursor.clearSelection().endSelection();
1648 blur();
1649 });
1650 $(window).on('blur', windowBlur);
1651 });
1652 function windowBlur() { // blur event also fired on window, just switching
1653 clearTimeout(blurTimeout); // tabs/windows, not intentional blur
1654 if (cursor.selection) cursor.selection.jQ.addClass('mq-blur');
1655 blur();
1656 }
1657 function blur() { // not directly in the textarea blur handler so as to be
1658 cursor.hide().parent.blur(); // synchronous with/in the same frame as
1659 ctrlr.container.removeClass('mq-focused'); // clearing/blurring selection
1660 $(window).off('blur', windowBlur);
1661 }
1662 ctrlr.blurred = true;
1663 cursor.hide().parent.blur();
1664 };
1665});
1666
1667/**
1668 * TODO: I wanted to move MathBlock::focus and blur here, it would clean
1669 * up lots of stuff like, TextBlock::focus is set to MathBlock::focus
1670 * and TextBlock::blur calls MathBlock::blur, when instead they could
1671 * use inheritance and super_.
1672 *
1673 * Problem is, there's lots of calls to .focus()/.blur() on nodes
1674 * outside Controller::focusBlurEvents(), such as .postOrder('blur') on
1675 * insertion, which if MathBlock::blur becomes Node::blur, would add the
1676 * 'blur' CSS class to all Symbol's (because .isEmpty() is true for all
1677 * of them).
1678 *
1679 * I'm not even sure there aren't other troublesome calls to .focus() or
1680 * .blur(), so this is TODO for now.
1681 */
1682/*****************************************
1683 * Deals with the browser DOM events from
1684 * interaction with the typist.
1685 ****************************************/
1686
1687Controller.open(function(_) {
1688 _.keystroke = function(key, evt) {
1689 this.cursor.parent.keystroke(key, evt, this);
1690 };
1691});
1692
1693Node.open(function(_) {
1694 _.keystroke = function(key, e, ctrlr) {
1695 var cursor = ctrlr.cursor;
1696
1697 switch (key) {
1698 case 'Ctrl-Shift-Backspace':
1699 case 'Ctrl-Backspace':
1700 ctrlr.ctrlDeleteDir(L);
1701 break;
1702
1703 case 'Shift-Backspace':
1704 case 'Backspace':
1705 ctrlr.backspace();
1706 break;
1707
1708 // Tab or Esc -> go one block right if it exists, else escape right.
1709 case 'Esc':
1710 case 'Tab':
1711 ctrlr.escapeDir(R, key, e);
1712 return;
1713
1714 // Shift-Tab -> go one block left if it exists, else escape left.
1715 case 'Shift-Tab':
1716 case 'Shift-Esc':
1717 ctrlr.escapeDir(L, key, e);
1718 return;
1719
1720 // End -> move to the end of the current block.
1721 case 'End':
1722 ctrlr.notify('move').cursor.insAtRightEnd(cursor.parent);
1723 break;
1724
1725 // Ctrl-End -> move all the way to the end of the root block.
1726 case 'Ctrl-End':
1727 ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
1728 break;
1729
1730 // Shift-End -> select to the end of the current block.
1731 case 'Shift-End':
1732 while (cursor[R]) {
1733 ctrlr.selectRight();
1734 }
1735 break;
1736
1737 // Ctrl-Shift-End -> select to the end of the root block.
1738 case 'Ctrl-Shift-End':
1739 while (cursor[R] || cursor.parent !== ctrlr.root) {
1740 ctrlr.selectRight();
1741 }
1742 break;
1743
1744 // Home -> move to the start of the root block or the current block.
1745 case 'Home':
1746 ctrlr.notify('move').cursor.insAtLeftEnd(cursor.parent);
1747 break;
1748
1749 // Ctrl-Home -> move to the start of the current block.
1750 case 'Ctrl-Home':
1751 ctrlr.notify('move').cursor.insAtLeftEnd(ctrlr.root);
1752 break;
1753
1754 // Shift-Home -> select to the start of the current block.
1755 case 'Shift-Home':
1756 while (cursor[L]) {
1757 ctrlr.selectLeft();
1758 }
1759 break;
1760
1761 // Ctrl-Shift-Home -> move to the start of the root block.
1762 case 'Ctrl-Shift-Home':
1763 while (cursor[L] || cursor.parent !== ctrlr.root) {
1764 ctrlr.selectLeft();
1765 }
1766 break;
1767
1768 case 'Left': ctrlr.moveLeft(); break;
1769 case 'Shift-Left': ctrlr.selectLeft(); break;
1770 case 'Ctrl-Left': break;
1771
1772 case 'Right': ctrlr.moveRight(); break;
1773 case 'Shift-Right': ctrlr.selectRight(); break;
1774 case 'Ctrl-Right': break;
1775
1776 case 'Up': ctrlr.moveUp(); break;
1777 case 'Down': ctrlr.moveDown(); break;
1778
1779 case 'Shift-Up':
1780 if (cursor[L]) {
1781 while (cursor[L]) ctrlr.selectLeft();
1782 } else {
1783 ctrlr.selectLeft();
1784 }
1785
1786 case 'Shift-Down':
1787 if (cursor[R]) {
1788 while (cursor[R]) ctrlr.selectRight();
1789 }
1790 else {
1791 ctrlr.selectRight();
1792 }
1793
1794 case 'Ctrl-Up': break;
1795 case 'Ctrl-Down': break;
1796
1797 case 'Ctrl-Shift-Del':
1798 case 'Ctrl-Del':
1799 ctrlr.ctrlDeleteDir(R);
1800 break;
1801
1802 case 'Shift-Del':
1803 case 'Del':
1804 ctrlr.deleteForward();
1805 break;
1806
1807 case 'Meta-A':
1808 case 'Ctrl-A':
1809 ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root);
1810 while (cursor[L]) ctrlr.selectLeft();
1811 break;
1812
1813 default:
1814 return;
1815 }
1816 e.preventDefault();
1817 ctrlr.scrollHoriz();
1818 };
1819
1820 _.moveOutOf = // called by Controller::escapeDir, moveDir
1821 _.moveTowards = // called by Controller::moveDir
1822 _.deleteOutOf = // called by Controller::deleteDir
1823 _.deleteTowards = // called by Controller::deleteDir
1824 _.unselectInto = // called by Controller::selectDir
1825 _.selectOutOf = // called by Controller::selectDir
1826 _.selectTowards = // called by Controller::selectDir
1827 function() { pray('overridden or never called on this node'); };
1828});
1829
1830Controller.open(function(_) {
1831 this.onNotify(function(e) {
1832 if (e === 'move' || e === 'upDown') this.show().clearSelection();
1833 });
1834 _.escapeDir = function(dir, key, e) {
1835 prayDirection(dir);
1836 var cursor = this.cursor;
1837
1838 // only prevent default of Tab if not in the root editable
1839 if (cursor.parent !== this.root) e.preventDefault();
1840
1841 // want to be a noop if in the root editable (in fact, Tab has an unrelated
1842 // default browser action if so)
1843 if (cursor.parent === this.root) return;
1844
1845 cursor.parent.moveOutOf(dir, cursor);
1846 return this.notify('move');
1847 };
1848
1849 optionProcessors.leftRightIntoCmdGoes = function(updown) {
1850 if (updown && updown !== 'up' && updown !== 'down') {
1851 throw '"up" or "down" required for leftRightIntoCmdGoes option, '
1852 + 'got "'+updown+'"';
1853 }
1854 return updown;
1855 };
1856 _.moveDir = function(dir) {
1857 prayDirection(dir);
1858 var cursor = this.cursor, updown = cursor.options.leftRightIntoCmdGoes;
1859
1860 if (cursor.selection) {
1861 cursor.insDirOf(dir, cursor.selection.ends[dir]);
1862 }
1863 else if (cursor[dir]) cursor[dir].moveTowards(dir, cursor, updown);
1864 else cursor.parent.moveOutOf(dir, cursor, updown);
1865
1866 return this.notify('move');
1867 };
1868 _.moveLeft = function() { return this.moveDir(L); };
1869 _.moveRight = function() { return this.moveDir(R); };
1870
1871 /**
1872 * moveUp and moveDown have almost identical algorithms:
1873 * - first check left and right, if so insAtLeft/RightEnd of them
1874 * - else check the parent's 'upOutOf'/'downOutOf' property:
1875 * + if it's a function, call it with the cursor as the sole argument and
1876 * use the return value as if it were the value of the property
1877 * + if it's a Node, jump up or down into it:
1878 * - if there is a cached Point in the block, insert there
1879 * - else, seekHoriz within the block to the current x-coordinate (to be
1880 * as close to directly above/below the current position as possible)
1881 * + unless it's exactly `true`, stop bubbling
1882 */
1883 _.moveUp = function() { return moveUpDown(this, 'up'); };
1884 _.moveDown = function() { return moveUpDown(this, 'down'); };
1885 function moveUpDown(self, dir) {
1886 var cursor = self.notify('upDown').cursor;
1887 var dirInto = dir+'Into', dirOutOf = dir+'OutOf';
1888 if (cursor[R][dirInto]) cursor.insAtLeftEnd(cursor[R][dirInto]);
1889 else if (cursor[L][dirInto]) cursor.insAtRightEnd(cursor[L][dirInto]);
1890 else {
1891 cursor.parent.bubble(function(ancestor) {
1892 var prop = ancestor[dirOutOf];
1893 if (prop) {
1894 if (typeof prop === 'function') prop = ancestor[dirOutOf](cursor);
1895 if (prop instanceof Node) cursor.jumpUpDown(ancestor, prop);
1896 if (prop !== true) return false;
1897 }
1898 });
1899 }
1900 return self;
1901 }
1902 this.onNotify(function(e) { if (e !== 'upDown') this.upDownCache = {}; });
1903
1904 this.onNotify(function(e) { if (e === 'edit') this.show().deleteSelection(); });
1905 _.deleteDir = function(dir) {
1906 prayDirection(dir);
1907 var cursor = this.cursor;
1908
1909 var hadSelection = cursor.selection;
1910 this.notify('edit'); // deletes selection if present
1911 if (!hadSelection) {
1912 if (cursor[dir]) cursor[dir].deleteTowards(dir, cursor);
1913 else cursor.parent.deleteOutOf(dir, cursor);
1914 }
1915
1916 if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
1917 if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
1918 cursor.parent.bubble('reflow');
1919
1920 return this;
1921 };
1922 _.ctrlDeleteDir = function(dir) {
1923 prayDirection(dir);
1924 var cursor = this.cursor;
1925 if (!cursor[L] || cursor.selection) return ctrlr.deleteDir();
1926
1927 this.notify('edit');
1928 Fragment(cursor.parent.ends[L], cursor[L]).remove();
1929 cursor.insAtDirEnd(L, cursor.parent);
1930
1931 if (cursor[L].siblingDeleted) cursor[L].siblingDeleted(cursor.options, R);
1932 if (cursor[R].siblingDeleted) cursor[R].siblingDeleted(cursor.options, L);
1933 cursor.parent.bubble('reflow');
1934
1935 return this;
1936 };
1937 _.backspace = function() { return this.deleteDir(L); };
1938 _.deleteForward = function() { return this.deleteDir(R); };
1939
1940 this.onNotify(function(e) { if (e !== 'select') this.endSelection(); });
1941 _.selectDir = function(dir) {
1942 var cursor = this.notify('select').cursor, seln = cursor.selection;
1943 prayDirection(dir);
1944
1945 if (!cursor.anticursor) cursor.startSelection();
1946
1947 var node = cursor[dir];
1948 if (node) {
1949 // "if node we're selecting towards is inside selection (hence retracting)
1950 // and is on the *far side* of the selection (hence is only node selected)
1951 // and the anticursor is *inside* that node, not just on the other side"
1952 if (seln && seln.ends[dir] === node && cursor.anticursor[-dir] !== node) {
1953 node.unselectInto(dir, cursor);
1954 }
1955 else node.selectTowards(dir, cursor);
1956 }
1957 else cursor.parent.selectOutOf(dir, cursor);
1958
1959 cursor.clearSelection();
1960 cursor.select() || cursor.show();
1961 };
1962 _.selectLeft = function() { return this.selectDir(L); };
1963 _.selectRight = function() { return this.selectDir(R); };
1964});
1965// Parser MathBlock
1966var latexMathParser = (function() {
1967 function commandToBlock(cmd) { // can also take in a Fragment
1968 var block = MathBlock();
1969 cmd.adopt(block, 0, 0);
1970 return block;
1971 }
1972 function joinBlocks(blocks) {
1973 var firstBlock = blocks[0] || MathBlock();
1974
1975 for (var i = 1; i < blocks.length; i += 1) {
1976 blocks[i].children().adopt(firstBlock, firstBlock.ends[R], 0);
1977 }
1978
1979 return firstBlock;
1980 }
1981
1982 var string = Parser.string;
1983 var regex = Parser.regex;
1984 var letter = Parser.letter;
1985 var any = Parser.any;
1986 var optWhitespace = Parser.optWhitespace;
1987 var succeed = Parser.succeed;
1988 var fail = Parser.fail;
1989
1990 // Parsers yielding either MathCommands, or Fragments of MathCommands
1991 // (either way, something that can be adopted by a MathBlock)
1992 var variable = letter.map(function(c) { return Letter(c); });
1993 var symbol = regex(/^[^${}\\_^]/).map(function(c) { return VanillaSymbol(c); });
1994
1995 var controlSequence =
1996 regex(/^[^\\a-eg-zA-Z]/) // hotfix #164; match MathBlock::write
1997 .or(string('\\').then(
1998 regex(/^[a-z]+/i)
1999 .or(regex(/^\s+/).result(' '))
2000 .or(any)
2001 )).then(function(ctrlSeq) {
2002 var cmdKlass = LatexCmds[ctrlSeq];
2003
2004 if (cmdKlass) {
2005 return cmdKlass(ctrlSeq).parser();
2006 }
2007 else {
2008 return fail('unknown command: \\'+ctrlSeq);
2009 }
2010 })
2011 ;
2012
2013 var command =
2014 controlSequence
2015 .or(variable)
2016 .or(symbol)
2017 ;
2018
2019 // Parsers yielding MathBlocks
2020 var mathGroup = string('{').then(function() { return mathSequence; }).skip(string('}'));
2021 var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock)));
2022 var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace);
2023
2024 var optMathBlock =
2025 string('[').then(
2026 mathBlock.then(function(block) {
2027 return block.join('latex') !== ']' ? succeed(block) : fail();
2028 })
2029 .many().map(joinBlocks).skip(optWhitespace)
2030 ).skip(string(']'))
2031 ;
2032
2033 var latexMath = mathSequence;
2034
2035 latexMath.block = mathBlock;
2036 latexMath.optBlock = optMathBlock;
2037 return latexMath;
2038})();
2039
2040Controller.open(function(_, super_) {
2041 _.exportLatex = function() {
2042 return this.root.latex().replace(/(\\[a-z]+) (?![a-z])/ig,'$1');
2043 };
2044 _.writeLatex = function(latex) {
2045 var cursor = this.notify('edit').cursor;
2046
2047 var all = Parser.all;
2048 var eof = Parser.eof;
2049
2050 var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
2051
2052 if (block && !block.isEmpty()) {
2053 block.children().adopt(cursor.parent, cursor[L], cursor[R]);
2054 var jQ = block.jQize();
2055 jQ.insertBefore(cursor.jQ);
2056 cursor[L] = block.ends[R];
2057 block.finalizeInsert(cursor.options, cursor);
2058 if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
2059 if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
2060 cursor.parent.bubble('reflow');
2061 }
2062
2063 return this;
2064 };
2065 _.renderLatexMath = function(latex) {
2066 var root = this.root, cursor = this.cursor;
2067
2068 var all = Parser.all;
2069 var eof = Parser.eof;
2070
2071 var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
2072
2073 root.eachChild('postOrder', 'dispose');
2074 root.ends[L] = root.ends[R] = 0;
2075
2076 if (block) {
2077 block.children().adopt(root, 0, 0);
2078 }
2079
2080 var jQ = root.jQ;
2081
2082 if (block) {
2083 var html = block.join('html');
2084 jQ.html(html);
2085 root.jQize(jQ.children());
2086 root.finalizeInsert(cursor.options);
2087 }
2088 else {
2089 jQ.empty();
2090 }
2091
2092 delete cursor.selection;
2093 cursor.insAtRightEnd(root);
2094 };
2095 _.renderLatexText = function(latex) {
2096 var root = this.root, cursor = this.cursor;
2097
2098 root.jQ.children().slice(1).remove();
2099 root.eachChild('postOrder', 'dispose');
2100 root.ends[L] = root.ends[R] = 0;
2101 delete cursor.selection;
2102 cursor.show().insAtRightEnd(root);
2103
2104 var regex = Parser.regex;
2105 var string = Parser.string;
2106 var eof = Parser.eof;
2107 var all = Parser.all;
2108
2109 // Parser RootMathCommand
2110 var mathMode = string('$').then(latexMathParser)
2111 // because TeX is insane, math mode doesn't necessarily
2112 // have to end. So we allow for the case that math mode
2113 // continues to the end of the stream.
2114 .skip(string('$').or(eof))
2115 .map(function(block) {
2116 // HACK FIXME: this shouldn't have to have access to cursor
2117 var rootMathCommand = RootMathCommand(cursor);
2118
2119 rootMathCommand.createBlocks();
2120 var rootMathBlock = rootMathCommand.ends[L];
2121 block.children().adopt(rootMathBlock, 0, 0);
2122
2123 return rootMathCommand;
2124 })
2125 ;
2126
2127 var escapedDollar = string('\\$').result('$');
2128 var textChar = escapedDollar.or(regex(/^[^$]/)).map(VanillaSymbol);
2129 var latexText = mathMode.or(textChar).many();
2130 var commands = latexText.skip(eof).or(all.result(false)).parse(latex);
2131
2132 if (commands) {
2133 for (var i = 0; i < commands.length; i += 1) {
2134 commands[i].adopt(root, root.ends[R], 0);
2135 }
2136
2137 root.jQize().appendTo(root.jQ);
2138
2139 root.finalizeInsert(cursor.options);
2140 }
2141 };
2142});
2143/********************************************************
2144 * Deals with mouse events for clicking, drag-to-select
2145 *******************************************************/
2146
2147Controller.open(function(_) {
2148 Options.p.ignoreNextMousedown = noop;
2149 _.delegateMouseEvents = function() {
2150 var ultimateRootjQ = this.root.jQ;
2151 //drag-to-select event handling
2152 this.container.bind('mousedown.mathquill', function(e) {
2153 var rootjQ = $(e.target).closest('.mq-root-block');
2154 var root = Node.byId[rootjQ.attr(mqBlockId) || ultimateRootjQ.attr(mqBlockId)];
2155 var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink;
2156 var textareaSpan = ctrlr.textareaSpan, textarea = ctrlr.textarea;
2157
2158 e.preventDefault(); // doesn't work in IE\u22648, but it's a one-line fix:
2159 e.target.unselectable = true; // http://jsbin.com/yagekiji/1
2160
2161 if (cursor.options.ignoreNextMousedown(e)) return;
2162 else cursor.options.ignoreNextMousedown = noop;
2163
2164 var target;
2165 function mousemove(e) { target = $(e.target); }
2166 function docmousemove(e) {
2167 if (!cursor.anticursor) cursor.startSelection();
2168 ctrlr.seek(target, e.pageX, e.pageY).cursor.select();
2169 target = undefined;
2170 }
2171 // outside rootjQ, the MathQuill node corresponding to the target (if any)
2172 // won't be inside this root, so don't mislead Controller::seek with it
2173
2174 function mouseup(e) {
2175 cursor.blink = blink;
2176 if (!cursor.selection) {
2177 if (ctrlr.editable) {
2178 cursor.show();
2179 }
2180 else {
2181 textareaSpan.detach();
2182 }
2183 }
2184
2185 // delete the mouse handlers now that we're not dragging anymore
2186 rootjQ.unbind('mousemove', mousemove);
2187 $(e.target.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup', mouseup);
2188 }
2189
2190 if (ctrlr.blurred) {
2191 if (!ctrlr.editable) rootjQ.prepend(textareaSpan);
2192 textarea.focus();
2193 }
2194
2195 cursor.blink = noop;
2196 ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection();
2197
2198 rootjQ.mousemove(mousemove);
2199 $(e.target.ownerDocument).mousemove(docmousemove).mouseup(mouseup);
2200 // listen on document not just body to not only hear about mousemove and
2201 // mouseup on page outside field, but even outside page, except iframes: https://github.com/mathquill/mathquill/commit/8c50028afcffcace655d8ae2049f6e02482346c5#commitcomment-6175800
2202 });
2203 }
2204});
2205
2206Controller.open(function(_) {
2207 _.seek = function(target, pageX, pageY) {
2208 var cursor = this.notify('select').cursor;
2209
2210 if (target) {
2211 var nodeId = target.attr(mqBlockId) || target.attr(mqCmdId);
2212 if (!nodeId) {
2213 var targetParent = target.parent();
2214 nodeId = targetParent.attr(mqBlockId) || targetParent.attr(mqCmdId);
2215 }
2216 }
2217 var node = nodeId ? Node.byId[nodeId] : this.root;
2218 pray('nodeId is the id of some Node that exists', node);
2219
2220 // don't clear selection until after getting node from target, in case
2221 // target was selection span, otherwise target will have no parent and will
2222 // seek from root, which is less accurate (e.g. fraction)
2223 cursor.clearSelection().show();
2224
2225 node.seek(pageX, cursor);
2226 this.scrollHoriz(); // before .selectFrom when mouse-selecting, so
2227 // always hits no-selection case in scrollHoriz and scrolls slower
2228 return this;
2229 };
2230});
2231/***********************************************
2232 * Horizontal panning for editable fields that
2233 * overflow their width
2234 **********************************************/
2235
2236Controller.open(function(_) {
2237 _.scrollHoriz = function() {
2238 var cursor = this.cursor, seln = cursor.selection;
2239 var rootRect = this.root.jQ[0].getBoundingClientRect();
2240 if (!seln) {
2241 var x = cursor.jQ[0].getBoundingClientRect().left;
2242 if (x > rootRect.right - 20) var scrollBy = x - (rootRect.right - 20);
2243 else if (x < rootRect.left + 20) var scrollBy = x - (rootRect.left + 20);
2244 else return;
2245 }
2246 else {
2247 var rect = seln.jQ[0].getBoundingClientRect();
2248 var overLeft = rect.left - (rootRect.left + 20);
2249 var overRight = rect.right - (rootRect.right - 20);
2250 if (seln.ends[L] === cursor[R]) {
2251 if (overLeft < 0) var scrollBy = overLeft;
2252 else if (overRight > 0) {
2253 if (rect.left - overRight < rootRect.left + 20) var scrollBy = overLeft;
2254 else var scrollBy = overRight;
2255 }
2256 else return;
2257 }
2258 else {
2259 if (overRight > 0) var scrollBy = overRight;
2260 else if (overLeft < 0) {
2261 if (rect.right - overLeft > rootRect.right - 20) var scrollBy = overRight;
2262 else var scrollBy = overLeft;
2263 }
2264 else return;
2265 }
2266 }
2267 this.root.jQ.stop().animate({ scrollLeft: '+=' + scrollBy}, 100);
2268 };
2269});
2270/*********************************************
2271 * Manage the MathQuill instance's textarea
2272 * (as owned by the Controller)
2273 ********************************************/
2274
2275Controller.open(function(_) {
2276 Options.p.substituteTextarea = function() {
2277 return $('<textarea autocapitalize=off autocomplete=off autocorrect=off ' +
2278 'spellcheck=false x-palm-disable-ste-all=true />')[0];
2279 };
2280 _.createTextarea = function() {
2281 var textareaSpan = this.textareaSpan = $('<span class="mq-textarea"></span>'),
2282 textarea = this.options.substituteTextarea();
2283 if (!textarea.nodeType) {
2284 throw 'substituteTextarea() must return a DOM element, got ' + textarea;
2285 }
2286 textarea = this.textarea = $(textarea).appendTo(textareaSpan);
2287
2288 var ctrlr = this;
2289 ctrlr.cursor.selectionChanged = function() { ctrlr.selectionChanged(); };
2290 ctrlr.container.bind('copy', function() { ctrlr.setTextareaSelection(); });
2291 };
2292 _.selectionChanged = function() {
2293 var ctrlr = this;
2294 forceIERedraw(ctrlr.container[0]);
2295
2296 // throttle calls to setTextareaSelection(), because setting textarea.value
2297 // and/or calling textarea.select() can have anomalously bad performance:
2298 // https://github.com/mathquill/mathquill/issues/43#issuecomment-1399080
2299 if (ctrlr.textareaSelectionTimeout === undefined) {
2300 ctrlr.textareaSelectionTimeout = setTimeout(function() {
2301 ctrlr.setTextareaSelection();
2302 });
2303 }
2304 };
2305 _.setTextareaSelection = function() {
2306 this.textareaSelectionTimeout = undefined;
2307 var latex = '';
2308 if (this.cursor.selection) {
2309 latex = this.cursor.selection.join('latex');
2310 if (this.options.statelessClipboard) {
2311 // FIXME: like paste, only this works for math fields; should ask parent
2312 latex = '$' + latex + '$';
2313 }
2314 }
2315 this.selectFn(latex);
2316 };
2317 _.staticMathTextareaEvents = function() {
2318 var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
2319 textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
2320
2321 this.container.prepend('<span class="mq-selectable">$'+ctrlr.exportLatex()+'$</span>');
2322 ctrlr.blurred = true;
2323 textarea.bind('cut paste', false)
2324 .focus(function() { ctrlr.blurred = false; }).blur(function() {
2325 if (cursor.selection) cursor.selection.clear();
2326 setTimeout(detach); //detaching during blur explodes in WebKit
2327 });
2328 function detach() {
2329 textareaSpan.detach();
2330 ctrlr.blurred = true;
2331 }
2332
2333 ctrlr.selectFn = function(text) {
2334 textarea.val(text);
2335 if (text) textarea.select();
2336 };
2337 };
2338 _.editablesTextareaEvents = function() {
2339 var ctrlr = this, root = ctrlr.root, cursor = ctrlr.cursor,
2340 textarea = ctrlr.textarea, textareaSpan = ctrlr.textareaSpan;
2341
2342 var keyboardEventsShim = saneKeyboardEvents(textarea, this);
2343 this.selectFn = function(text) { keyboardEventsShim.select(text); };
2344
2345 this.container.prepend(textareaSpan)
2346 .on('cut', function(e) {
2347 if (cursor.selection) {
2348 setTimeout(function() {
2349 ctrlr.notify('edit'); // deletes selection if present
2350 cursor.parent.bubble('reflow');
2351 });
2352 }
2353 });
2354
2355 this.focusBlurEvents();
2356 };
2357 _.typedText = function(ch) {
2358 if (ch === '\n') return this.handle('enter');
2359 var cursor = this.notify().cursor;
2360 cursor.parent.write(cursor, ch);
2361 this.scrollHoriz();
2362 };
2363 _.paste = function(text) {
2364 // TODO: document `statelessClipboard` config option in README, after
2365 // making it work like it should, that is, in both text and math mode
2366 // (currently only works in math fields, so worse than pointless, it
2367 // only gets in the way by \text{}-ifying pasted stuff and $-ifying
2368 // cut/copied LaTeX)
2369 if (this.options.statelessClipboard) {
2370 if (text.slice(0,1) === '$' && text.slice(-1) === '$') {
2371 text = text.slice(1, -1);
2372 }
2373 else {
2374 text = '\\text{'+text+'}';
2375 }
2376 }
2377 // FIXME: this always inserts math or a TextBlock, even in a RootTextBlock
2378 this.writeLatex(text).cursor.show();
2379 };
2380});
2381/*************************************************
2382 * Abstract classes of math blocks and commands.
2383 ************************************************/
2384
2385/**
2386 * Math tree node base class.
2387 * Some math-tree-specific extensions to Node.
2388 * Both MathBlock's and MathCommand's descend from it.
2389 */
2390var MathElement = P(Node, function(_, super_) {
2391 _.finalizeInsert = function(options, cursor) { // `cursor` param is only for
2392 // SupSub::contactWeld, and is deliberately only passed in by writeLatex,
2393 // see ea7307eb4fac77c149a11ffdf9a831df85247693
2394 var self = this;
2395 self.postOrder('finalizeTree', options);
2396 self.postOrder('contactWeld', cursor);
2397
2398 // note: this order is important.
2399 // empty elements need the empty box provided by blur to
2400 // be present in order for their dimensions to be measured
2401 // correctly by 'reflow' handlers.
2402 self.postOrder('blur');
2403
2404 self.postOrder('reflow');
2405 if (self[R].siblingCreated) self[R].siblingCreated(options, L);
2406 if (self[L].siblingCreated) self[L].siblingCreated(options, R);
2407 self.bubble('reflow');
2408 };
2409});
2410
2411/**
2412 * Commands and operators, like subscripts, exponents, or fractions.
2413 * Descendant commands are organized into blocks.
2414 */
2415var MathCommand = P(MathElement, function(_, super_) {
2416 _.init = function(ctrlSeq, htmlTemplate, textTemplate) {
2417 var cmd = this;
2418 super_.init.call(cmd);
2419
2420 if (!cmd.ctrlSeq) cmd.ctrlSeq = ctrlSeq;
2421 if (htmlTemplate) cmd.htmlTemplate = htmlTemplate;
2422 if (textTemplate) cmd.textTemplate = textTemplate;
2423 };
2424
2425 // obvious methods
2426 _.replaces = function(replacedFragment) {
2427 replacedFragment.disown();
2428 this.replacedFragment = replacedFragment;
2429 };
2430 _.isEmpty = function() {
2431 return this.foldChildren(true, function(isEmpty, child) {
2432 return isEmpty && child.isEmpty();
2433 });
2434 };
2435
2436 _.parser = function() {
2437 var block = latexMathParser.block;
2438 var self = this;
2439
2440 return block.times(self.numBlocks()).map(function(blocks) {
2441 self.blocks = blocks;
2442
2443 for (var i = 0; i < blocks.length; i += 1) {
2444 blocks[i].adopt(self, self.ends[R], 0);
2445 }
2446
2447 return self;
2448 });
2449 };
2450
2451 // createLeftOf(cursor) and the methods it calls
2452 _.createLeftOf = function(cursor) {
2453 var cmd = this;
2454 var replacedFragment = cmd.replacedFragment;
2455
2456 cmd.createBlocks();
2457 super_.createLeftOf.call(cmd, cursor);
2458 if (replacedFragment) {
2459 replacedFragment.adopt(cmd.ends[L], 0, 0);
2460 replacedFragment.jQ.appendTo(cmd.ends[L].jQ);
2461 }
2462 cmd.finalizeInsert(cursor.options);
2463 cmd.placeCursor(cursor);
2464 };
2465 _.createBlocks = function() {
2466 var cmd = this,
2467 numBlocks = cmd.numBlocks(),
2468 blocks = cmd.blocks = Array(numBlocks);
2469
2470 for (var i = 0; i < numBlocks; i += 1) {
2471 var newBlock = blocks[i] = MathBlock();
2472 newBlock.adopt(cmd, cmd.ends[R], 0);
2473 }
2474 };
2475 _.placeCursor = function(cursor) {
2476 //insert the cursor at the right end of the first empty child, searching
2477 //left-to-right, or if none empty, the right end child
2478 cursor.insAtRightEnd(this.foldChildren(this.ends[L], function(leftward, child) {
2479 return leftward.isEmpty() ? leftward : child;
2480 }));
2481 };
2482
2483 // editability methods: called by the cursor for editing, cursor movements,
2484 // and selection of the MathQuill tree, these all take in a direction and
2485 // the cursor
2486 _.moveTowards = function(dir, cursor, updown) {
2487 var updownInto = updown && this[updown+'Into'];
2488 cursor.insAtDirEnd(-dir, updownInto || this.ends[-dir]);
2489 };
2490 _.deleteTowards = function(dir, cursor) {
2491 if (this.isEmpty()) cursor[dir] = this.remove()[dir];
2492 else this.moveTowards(dir, cursor, null);
2493 };
2494 _.selectTowards = function(dir, cursor) {
2495 cursor[-dir] = this;
2496 cursor[dir] = this[dir];
2497 };
2498 _.selectChildren = function() {
2499 return Selection(this, this);
2500 };
2501 _.unselectInto = function(dir, cursor) {
2502 cursor.insAtDirEnd(-dir, cursor.anticursor.ancestors[this.id]);
2503 };
2504 _.seek = function(pageX, cursor) {
2505 function getBounds(node) {
2506 var bounds = {}
2507 bounds[L] = node.jQ.offset().left;
2508 bounds[R] = bounds[L] + node.jQ.outerWidth();
2509 return bounds;
2510 }
2511
2512 var cmd = this;
2513 var cmdBounds = getBounds(cmd);
2514
2515 if (pageX < cmdBounds[L]) return cursor.insLeftOf(cmd);
2516 if (pageX > cmdBounds[R]) return cursor.insRightOf(cmd);
2517
2518 var leftLeftBound = cmdBounds[L];
2519 cmd.eachChild(function(block) {
2520 var blockBounds = getBounds(block);
2521 if (pageX < blockBounds[L]) {
2522 // closer to this block's left bound, or the bound left of that?
2523 if (pageX - leftLeftBound < blockBounds[L] - pageX) {
2524 if (block[L]) cursor.insAtRightEnd(block[L]);
2525 else cursor.insLeftOf(cmd);
2526 }
2527 else cursor.insAtLeftEnd(block);
2528 return false;
2529 }
2530 else if (pageX > blockBounds[R]) {
2531 if (block[R]) leftLeftBound = blockBounds[R]; // continue to next block
2532 else { // last (rightmost) block
2533 // closer to this block's right bound, or the cmd's right bound?
2534 if (cmdBounds[R] - pageX < pageX - blockBounds[R]) {
2535 cursor.insRightOf(cmd);
2536 }
2537 else cursor.insAtRightEnd(block);
2538 }
2539 }
2540 else {
2541 block.seek(pageX, cursor);
2542 return false;
2543 }
2544 });
2545 }
2546
2547 // methods involved in creating and cross-linking with HTML DOM nodes
2548 /*
2549 They all expect an .htmlTemplate like
2550 '<span>&0</span>'
2551 or
2552 '<span><span>&0</span><span>&1</span></span>'
2553
2554 See html.test.js for more examples.
2555
2556 Requirements:
2557 - For each block of the command, there must be exactly one "block content
2558 marker" of the form '&<number>' where <number> is the 0-based index of the
2559 block. (Like the LaTeX \newcommand syntax, but with a 0-based rather than
2560 1-based index, because JavaScript because C because Dijkstra.)
2561 - The block content marker must be the sole contents of the containing
2562 element, there can't even be surrounding whitespace, or else we can't
2563 guarantee sticking to within the bounds of the block content marker when
2564 mucking with the HTML DOM.
2565 - The HTML not only must be well-formed HTML (of course), but also must
2566 conform to the XHTML requirements on tags, specifically all tags must
2567 either be self-closing (like '<br/>') or come in matching pairs.
2568 Close tags are never optional.
2569
2570 Note that &<number> isn't well-formed HTML; if you wanted a literal '&123',
2571 your HTML template would have to have '&123'.
2572 */
2573 _.numBlocks = function() {
2574 var matches = this.htmlTemplate.match(/&\d+/g);
2575 return matches ? matches.length : 0;
2576 };
2577 _.html = function() {
2578 // Render the entire math subtree rooted at this command, as HTML.
2579 // Expects .createBlocks() to have been called already, since it uses the
2580 // .blocks array of child blocks.
2581 //
2582 // See html.test.js for example templates and intended outputs.
2583 //
2584 // Given an .htmlTemplate as described above,
2585 // - insert the mathquill-command-id attribute into all top-level tags,
2586 // which will be used to set this.jQ in .jQize().
2587 // This is straightforward:
2588 // * tokenize into tags and non-tags
2589 // * loop through top-level tokens:
2590 // * add #cmdId attribute macro to top-level self-closing tags
2591 // * else add #cmdId attribute macro to top-level open tags
2592 // * skip the matching top-level close tag and all tag pairs
2593 // in between
2594 // - for each block content marker,
2595 // + replace it with the contents of the corresponding block,
2596 // rendered as HTML
2597 // + insert the mathquill-block-id attribute into the containing tag
2598 // This is even easier, a quick regex replace, since block tags cannot
2599 // contain anything besides the block content marker.
2600 //
2601 // Two notes:
2602 // - The outermost loop through top-level tokens should never encounter any
2603 // top-level close tags, because we should have first encountered a
2604 // matching top-level open tag, all inner tags should have appeared in
2605 // matching pairs and been skipped, and then we should have skipped the
2606 // close tag in question.
2607 // - All open tags should have matching close tags, which means our inner
2608 // loop should always encounter a close tag and drop nesting to 0. If
2609 // a close tag is missing, the loop will continue until i >= tokens.length
2610 // and token becomes undefined. This will not infinite loop, even in
2611 // production without pray(), because it will then TypeError on .slice().
2612
2613 var cmd = this;
2614 var blocks = cmd.blocks;
2615 var cmdId = ' mathquill-command-id=' + cmd.id;
2616 var tokens = cmd.htmlTemplate.match(/<[^<>]+>|[^<>]+/g);
2617
2618 pray('no unmatched angle brackets', tokens.join('') === this.htmlTemplate);
2619
2620 // add cmdId to all top-level tags
2621 for (var i = 0, token = tokens[0]; token; i += 1, token = tokens[i]) {
2622 // top-level self-closing tags
2623 if (token.slice(-2) === '/>') {
2624 tokens[i] = token.slice(0,-2) + cmdId + '/>';
2625 }
2626 // top-level open tags
2627 else if (token.charAt(0) === '<') {
2628 pray('not an unmatched top-level close tag', token.charAt(1) !== '/');
2629
2630 tokens[i] = token.slice(0,-1) + cmdId + '>';
2631
2632 // skip matching top-level close tag and all tag pairs in between
2633 var nesting = 1;
2634 do {
2635 i += 1, token = tokens[i];
2636 pray('no missing close tags', token);
2637 // close tags
2638 if (token.slice(0,2) === '</') {
2639 nesting -= 1;
2640 }
2641 // non-self-closing open tags
2642 else if (token.charAt(0) === '<' && token.slice(-2) !== '/>') {
2643 nesting += 1;
2644 }
2645 } while (nesting > 0);
2646 }
2647 }
2648 return tokens.join('').replace(/>&(\d+)/g, function($0, $1) {
2649 return ' mathquill-block-id=' + blocks[$1].id + '>' + blocks[$1].join('html');
2650 });
2651 };
2652
2653 // methods to export a string representation of the math tree
2654 _.latex = function() {
2655 return this.foldChildren(this.ctrlSeq, function(latex, child) {
2656 return latex + '{' + (child.latex() || ' ') + '}';
2657 });
2658 };
2659 _.textTemplate = [''];
2660 _.text = function() {
2661 var cmd = this, i = 0;
2662 return cmd.foldChildren(cmd.textTemplate[i], function(text, child) {
2663 i += 1;
2664 var child_text = child.text();
2665 if (text && cmd.textTemplate[i] === '('
2666 && child_text[0] === '(' && child_text.slice(-1) === ')')
2667 return text + child_text.slice(1, -1) + cmd.textTemplate[i];
2668 return text + child.text() + (cmd.textTemplate[i] || '');
2669 });
2670 };
2671});
2672
2673/**
2674 * Lightweight command without blocks or children.
2675 */
2676var Symbol = P(MathCommand, function(_, super_) {
2677 _.init = function(ctrlSeq, html, text) {
2678 if (!text) text = ctrlSeq && ctrlSeq.length > 1 ? ctrlSeq.slice(1) : ctrlSeq;
2679
2680 super_.init.call(this, ctrlSeq, html, [ text ]);
2681 };
2682
2683 _.parser = function() { return Parser.succeed(this); };
2684 _.numBlocks = function() { return 0; };
2685
2686 _.replaces = function(replacedFragment) {
2687 replacedFragment.remove();
2688 };
2689 _.createBlocks = noop;
2690
2691 _.moveTowards = function(dir, cursor) {
2692 cursor.jQ.insDirOf(dir, this.jQ);
2693 cursor[-dir] = this;
2694 cursor[dir] = this[dir];
2695 };
2696 _.deleteTowards = function(dir, cursor) {
2697 cursor[dir] = this.remove()[dir];
2698 };
2699 _.seek = function(pageX, cursor) {
2700 // insert at whichever side the click was closer to
2701 if (pageX - this.jQ.offset().left < this.jQ.outerWidth()/2)
2702 cursor.insLeftOf(this);
2703 else
2704 cursor.insRightOf(this);
2705 };
2706
2707 _.latex = function(){ return this.ctrlSeq; };
2708 _.text = function(){ return this.textTemplate; };
2709 _.placeCursor = noop;
2710 _.isEmpty = function(){ return true; };
2711});
2712var VanillaSymbol = P(Symbol, function(_, super_) {
2713 _.init = function(ch, html) {
2714 super_.init.call(this, ch, '<span>'+(html || ch)+'</span>');
2715 };
2716});
2717var BinaryOperator = P(Symbol, function(_, super_) {
2718 _.init = function(ctrlSeq, html, text) {
2719 super_.init.call(this,
2720 ctrlSeq, '<span class="mq-binary-operator">'+html+'</span>', text
2721 );
2722 };
2723});
2724
2725/**
2726 * Children and parent of MathCommand's. Basically partitions all the
2727 * symbols and operators that descend (in the Math DOM tree) from
2728 * ancestor operators.
2729 */
2730var MathBlock = P(MathElement, function(_, super_) {
2731 _.join = function(methodName) {
2732 return this.foldChildren('', function(fold, child) {
2733 return fold + child[methodName]();
2734 });
2735 };
2736 _.html = function() { return this.join('html'); };
2737 _.latex = function() { return this.join('latex'); };
2738 _.text = function() {
2739 return (this.ends[L] === this.ends[R] && this.ends[L] !== 0) ?
2740 this.ends[L].text() :
2741 this.join('text')
2742 ;
2743 };
2744
2745 _.keystroke = function(key, e, ctrlr) {
2746 if (ctrlr.options.spaceBehavesLikeTab
2747 && (key === 'Spacebar' || key === 'Shift-Spacebar')) {
2748 e.preventDefault();
2749 ctrlr.escapeDir(key === 'Shift-Spacebar' ? L : R, key, e);
2750 return;
2751 }
2752 return super_.keystroke.apply(this, arguments);
2753 };
2754
2755 // editability methods: called by the cursor for editing, cursor movements,
2756 // and selection of the MathQuill tree, these all take in a direction and
2757 // the cursor
2758 _.moveOutOf = function(dir, cursor, updown) {
2759 var updownInto = updown && this.parent[updown+'Into'];
2760 if (!updownInto && this[dir]) cursor.insAtDirEnd(-dir, this[dir]);
2761 else cursor.insDirOf(dir, this.parent);
2762 };
2763 _.selectOutOf = function(dir, cursor) {
2764 cursor.insDirOf(dir, this.parent);
2765 };
2766 _.deleteOutOf = function(dir, cursor) {
2767 cursor.unwrapGramp();
2768 };
2769 _.seek = function(pageX, cursor) {
2770 var node = this.ends[R];
2771 if (!node || node.jQ.offset().left + node.jQ.outerWidth() < pageX) {
2772 return cursor.insAtRightEnd(this);
2773 }
2774 if (pageX < this.ends[L].jQ.offset().left) return cursor.insAtLeftEnd(this);
2775 while (pageX < node.jQ.offset().left) node = node[L];
2776 return node.seek(pageX, cursor);
2777 };
2778 _.chToCmd = function(ch) {
2779 var cons;
2780 // exclude f because it gets a dedicated command with more spacing
2781 if (ch.match(/^[a-eg-zA-Z]$/))
2782 return Letter(ch);
2783 else if (/^\d$/.test(ch))
2784 return Digit(ch);
2785 else if (cons = CharCmds[ch] || LatexCmds[ch])
2786 return cons(ch);
2787 else
2788 return VanillaSymbol(ch);
2789 };
2790 _.write = function(cursor, ch) {
2791 var cmd = this.chToCmd(ch);
2792 if (cursor.selection) cmd.replaces(cursor.replaceSelection());
2793 cmd.createLeftOf(cursor.show());
2794 };
2795
2796 _.focus = function() {
2797 this.jQ.addClass('mq-hasCursor');
2798 this.jQ.removeClass('mq-empty');
2799
2800 return this;
2801 };
2802 _.blur = function() {
2803 this.jQ.removeClass('mq-hasCursor');
2804 if (this.isEmpty())
2805 this.jQ.addClass('mq-empty');
2806
2807 return this;
2808 };
2809});
2810
2811API.StaticMath = function(APIClasses) {
2812 return P(APIClasses.AbstractMathQuill, function(_, super_) {
2813 this.RootBlock = MathBlock;
2814 _.__mathquillify = function() {
2815 super_.__mathquillify.call(this, 'mq-math-mode');
2816 this.__controller.delegateMouseEvents();
2817 this.__controller.staticMathTextareaEvents();
2818 return this;
2819 };
2820 _.init = function() {
2821 super_.init.apply(this, arguments);
2822 this.__controller.root.postOrder(
2823 'registerInnerField', this.innerFields = [], APIClasses.MathField);
2824 };
2825 _.latex = function() {
2826 var returned = super_.latex.apply(this, arguments);
2827 if (arguments.length > 0) {
2828 this.__controller.root.postOrder(
2829 'registerInnerField', this.innerFields = [], APIClasses.MathField);
2830 }
2831 return returned;
2832 };
2833 });
2834};
2835
2836var RootMathBlock = P(MathBlock, RootBlockMixin);
2837API.MathField = function(APIClasses) {
2838 return P(APIClasses.EditableField, function(_, super_) {
2839 this.RootBlock = RootMathBlock;
2840 _.__mathquillify = function(opts, interfaceVersion) {
2841 this.config(opts);
2842 if (interfaceVersion > 1) this.__controller.root.reflow = noop;
2843 super_.__mathquillify.call(this, 'mq-editable-field mq-math-mode');
2844 delete this.__controller.root.reflow;
2845 return this;
2846 };
2847 });
2848};
2849/*************************************************
2850 * Abstract classes of text blocks
2851 ************************************************/
2852
2853/**
2854 * Blocks of plain text, with one or two TextPiece's as children.
2855 * Represents flat strings of typically serif-font Roman characters, as
2856 * opposed to hierchical, nested, tree-structured math.
2857 * Wraps a single HTMLSpanElement.
2858 */
2859var TextBlock = P(Node, function(_, super_) {
2860 _.ctrlSeq = '\\text';
2861
2862 _.replaces = function(replacedText) {
2863 if (replacedText instanceof Fragment)
2864 this.replacedText = replacedText.remove().jQ.text();
2865 else if (typeof replacedText === 'string')
2866 this.replacedText = replacedText;
2867 };
2868
2869 _.jQadd = function(jQ) {
2870 super_.jQadd.call(this, jQ);
2871 if (this.ends[L]) this.ends[L].jQadd(this.jQ[0].firstChild);
2872 };
2873
2874 _.createLeftOf = function(cursor) {
2875 var textBlock = this;
2876 super_.createLeftOf.call(this, cursor);
2877
2878 if (textBlock[R].siblingCreated) textBlock[R].siblingCreated(cursor.options, L);
2879 if (textBlock[L].siblingCreated) textBlock[L].siblingCreated(cursor.options, R);
2880 textBlock.bubble('reflow');
2881
2882 cursor.insAtRightEnd(textBlock);
2883
2884 if (textBlock.replacedText)
2885 for (var i = 0; i < textBlock.replacedText.length; i += 1)
2886 textBlock.write(cursor, textBlock.replacedText.charAt(i));
2887 };
2888
2889 _.parser = function() {
2890 var textBlock = this;
2891
2892 // TODO: correctly parse text mode
2893 var string = Parser.string;
2894 var regex = Parser.regex;
2895 var optWhitespace = Parser.optWhitespace;
2896 return optWhitespace
2897 .then(string('{')).then(regex(/^[^}]*/)).skip(string('}'))
2898 .map(function(text) {
2899 if (text.length === 0) return Fragment();
2900
2901 TextPiece(text).adopt(textBlock, 0, 0);
2902 return textBlock;
2903 })
2904 ;
2905 };
2906
2907 _.textContents = function() {
2908 return this.foldChildren('', function(text, child) {
2909 return text + child.text;
2910 });
2911 };
2912 _.text = function() { return '"' + this.textContents() + '"'; };
2913 _.latex = function() {
2914 var contents = this.textContents();
2915 if (contents.length === 0) return '';
2916 return '\\text{' + contents + '}';
2917 };
2918 _.html = function() {
2919 return (
2920 '<span class="mq-text-mode" mathquill-command-id='+this.id+'>'
2921 + this.textContents()
2922 + '</span>'
2923 );
2924 };
2925
2926 // editability methods: called by the cursor for editing, cursor movements,
2927 // and selection of the MathQuill tree, these all take in a direction and
2928 // the cursor
2929 _.moveTowards = function(dir, cursor) { cursor.insAtDirEnd(-dir, this); };
2930 _.moveOutOf = function(dir, cursor) { cursor.insDirOf(dir, this); };
2931 _.unselectInto = _.moveTowards;
2932
2933 // TODO: make these methods part of a shared mixin or something.
2934 _.selectTowards = MathCommand.prototype.selectTowards;
2935 _.deleteTowards = MathCommand.prototype.deleteTowards;
2936
2937 _.selectOutOf = function(dir, cursor) {
2938 cursor.insDirOf(dir, this);
2939 };
2940 _.deleteOutOf = function(dir, cursor) {
2941 // backspace and delete at ends of block don't unwrap
2942 if (this.isEmpty()) cursor.insRightOf(this);
2943 };
2944 _.write = function(cursor, ch) {
2945 cursor.show().deleteSelection();
2946
2947 if (ch !== '$') {
2948 if (!cursor[L]) TextPiece(ch).createLeftOf(cursor);
2949 else cursor[L].appendText(ch);
2950 }
2951 else if (this.isEmpty()) {
2952 cursor.insRightOf(this);
2953 VanillaSymbol('\\$','$').createLeftOf(cursor);
2954 }
2955 else if (!cursor[R]) cursor.insRightOf(this);
2956 else if (!cursor[L]) cursor.insLeftOf(this);
2957 else { // split apart
2958 var leftBlock = TextBlock();
2959 var leftPc = this.ends[L];
2960 leftPc.disown().jQ.detach();
2961 leftPc.adopt(leftBlock, 0, 0);
2962
2963 cursor.insLeftOf(this);
2964 super_.createLeftOf.call(leftBlock, cursor);
2965 }
2966 };
2967
2968 _.seek = function(pageX, cursor) {
2969 cursor.hide();
2970 var textPc = fuseChildren(this);
2971
2972 // insert cursor at approx position in DOMTextNode
2973 var avgChWidth = this.jQ.width()/this.text.length;
2974 var approxPosition = Math.round((pageX - this.jQ.offset().left)/avgChWidth);
2975 if (approxPosition <= 0) cursor.insAtLeftEnd(this);
2976 else if (approxPosition >= textPc.text.length) cursor.insAtRightEnd(this);
2977 else cursor.insLeftOf(textPc.splitRight(approxPosition));
2978
2979 // move towards mousedown (pageX)
2980 var displ = pageX - cursor.show().offset().left; // displacement
2981 var dir = displ && displ < 0 ? L : R;
2982 var prevDispl = dir;
2983 // displ * prevDispl > 0 iff displacement direction === previous direction
2984 while (cursor[dir] && displ * prevDispl > 0) {
2985 cursor[dir].moveTowards(dir, cursor);
2986 prevDispl = displ;
2987 displ = pageX - cursor.offset().left;
2988 }
2989 if (dir*displ < -dir*prevDispl) cursor[-dir].moveTowards(-dir, cursor);
2990
2991 if (!cursor.anticursor) {
2992 // about to start mouse-selecting, the anticursor is gonna get put here
2993 this.anticursorPosition = cursor[L] && cursor[L].text.length;
2994 // ^ get it? 'cos if there's no cursor[L], it's 0... I'm a terrible person.
2995 }
2996 else if (cursor.anticursor.parent === this) {
2997 // mouse-selecting within this TextBlock, re-insert the anticursor
2998 var cursorPosition = cursor[L] && cursor[L].text.length;;
2999 if (this.anticursorPosition === cursorPosition) {
3000 cursor.anticursor = Point.copy(cursor);
3001 }
3002 else {
3003 if (this.anticursorPosition < cursorPosition) {
3004 var newTextPc = cursor[L].splitRight(this.anticursorPosition);
3005 cursor[L] = newTextPc;
3006 }
3007 else {
3008 var newTextPc = cursor[R].splitRight(this.anticursorPosition - cursorPosition);
3009 }
3010 cursor.anticursor = Point(this, newTextPc[L], newTextPc);
3011 }
3012 }
3013 };
3014
3015 _.blur = function(cursor) {
3016 MathBlock.prototype.blur.call(this);
3017 if (!cursor) return;
3018 if (this.textContents() === '') {
3019 this.remove();
3020 if (cursor[L] === this) cursor[L] = this[L];
3021 else if (cursor[R] === this) cursor[R] = this[R];
3022 }
3023 else fuseChildren(this);
3024 };
3025
3026 function fuseChildren(self) {
3027 self.jQ[0].normalize();
3028
3029 var textPcDom = self.jQ[0].firstChild;
3030 if (!textPcDom) return;
3031 pray('only node in TextBlock span is Text node', textPcDom.nodeType === 3);
3032 // nodeType === 3 has meant a Text node since ancient times:
3033 // http://reference.sitepoint.com/javascript/Node/nodeType
3034
3035 var textPc = TextPiece(textPcDom.data);
3036 textPc.jQadd(textPcDom);
3037
3038 self.children().disown();
3039 return textPc.adopt(self, 0, 0);
3040 }
3041
3042 _.focus = MathBlock.prototype.focus;
3043});
3044
3045/**
3046 * Piece of plain text, with a TextBlock as a parent and no children.
3047 * Wraps a single DOMTextNode.
3048 * For convenience, has a .text property that's just a JavaScript string
3049 * mirroring the text contents of the DOMTextNode.
3050 * Text contents must always be nonempty.
3051 */
3052var TextPiece = P(Node, function(_, super_) {
3053 _.init = function(text) {
3054 super_.init.call(this);
3055 this.text = text;
3056 };
3057 _.jQadd = function(dom) { this.dom = dom; this.jQ = $(dom); };
3058 _.jQize = function() {
3059 return this.jQadd(document.createTextNode(this.text));
3060 };
3061 _.appendText = function(text) {
3062 this.text += text;
3063 this.dom.appendData(text);
3064 };
3065 _.prependText = function(text) {
3066 this.text = text + this.text;
3067 this.dom.insertData(0, text);
3068 };
3069 _.insTextAtDirEnd = function(text, dir) {
3070 prayDirection(dir);
3071 if (dir === R) this.appendText(text);
3072 else this.prependText(text);
3073 };
3074 _.splitRight = function(i) {
3075 var newPc = TextPiece(this.text.slice(i)).adopt(this.parent, this, this[R]);
3076 newPc.jQadd(this.dom.splitText(i));
3077 this.text = this.text.slice(0, i);
3078 return newPc;
3079 };
3080
3081 function endChar(dir, text) {
3082 return text.charAt(dir === L ? 0 : -1 + text.length);
3083 }
3084
3085 _.moveTowards = function(dir, cursor) {
3086 prayDirection(dir);
3087
3088 var ch = endChar(-dir, this.text)
3089
3090 var from = this[-dir];
3091 if (from) from.insTextAtDirEnd(ch, dir);
3092 else TextPiece(ch).createDir(-dir, cursor);
3093
3094 return this.deleteTowards(dir, cursor);
3095 };
3096
3097 _.latex = function() { return this.text; };
3098
3099 _.deleteTowards = function(dir, cursor) {
3100 if (this.text.length > 1) {
3101 if (dir === R) {
3102 this.dom.deleteData(0, 1);
3103 this.text = this.text.slice(1);
3104 }
3105 else {
3106 // note that the order of these 2 lines is annoyingly important
3107 // (the second line mutates this.text.length)
3108 this.dom.deleteData(-1 + this.text.length, 1);
3109 this.text = this.text.slice(0, -1);
3110 }
3111 }
3112 else {
3113 this.remove();
3114 this.jQ.remove();
3115 cursor[dir] = this[dir];
3116 }
3117 };
3118
3119 _.selectTowards = function(dir, cursor) {
3120 prayDirection(dir);
3121 var anticursor = cursor.anticursor;
3122
3123 var ch = endChar(-dir, this.text)
3124
3125 if (anticursor[dir] === this) {
3126 var newPc = TextPiece(ch).createDir(dir, cursor);
3127 anticursor[dir] = newPc;
3128 cursor.insDirOf(dir, newPc);
3129 }
3130 else {
3131 var from = this[-dir];
3132 if (from) from.insTextAtDirEnd(ch, dir);
3133 else {
3134 var newPc = TextPiece(ch).createDir(-dir, cursor);
3135 newPc.jQ.insDirOf(-dir, cursor.selection.jQ);
3136 }
3137
3138 if (this.text.length === 1 && anticursor[-dir] === this) {
3139 anticursor[-dir] = this[-dir]; // `this` will be removed in deleteTowards
3140 }
3141 }
3142
3143 return this.deleteTowards(dir, cursor);
3144 };
3145});
3146
3147CharCmds.$ =
3148LatexCmds.text =
3149LatexCmds.textnormal =
3150LatexCmds.textrm =
3151LatexCmds.textup =
3152LatexCmds.textmd = TextBlock;
3153
3154function makeTextBlock(latex, tagName, attrs) {
3155 return P(TextBlock, {
3156 ctrlSeq: latex,
3157 htmlTemplate: '<'+tagName+' '+attrs+'>&0</'+tagName+'>'
3158 });
3159}
3160
3161LatexCmds.em = LatexCmds.italic = LatexCmds.italics =
3162LatexCmds.emph = LatexCmds.textit = LatexCmds.textsl =
3163 makeTextBlock('\\textit', 'i', 'class="mq-text-mode"');
3164LatexCmds.strong = LatexCmds.bold = LatexCmds.textbf =
3165 makeTextBlock('\\textbf', 'b', 'class="mq-text-mode"');
3166LatexCmds.sf = LatexCmds.textsf =
3167 makeTextBlock('\\textsf', 'span', 'class="mq-sans-serif mq-text-mode"');
3168LatexCmds.tt = LatexCmds.texttt =
3169 makeTextBlock('\\texttt', 'span', 'class="mq-monospace mq-text-mode"');
3170LatexCmds.textsc =
3171 makeTextBlock('\\textsc', 'span', 'style="font-variant:small-caps" class="mq-text-mode"');
3172LatexCmds.uppercase =
3173 makeTextBlock('\\uppercase', 'span', 'style="text-transform:uppercase" class="mq-text-mode"');
3174LatexCmds.lowercase =
3175 makeTextBlock('\\lowercase', 'span', 'style="text-transform:lowercase" class="mq-text-mode"');
3176
3177
3178var RootMathCommand = P(MathCommand, function(_, super_) {
3179 _.init = function(cursor) {
3180 super_.init.call(this, '$');
3181 this.cursor = cursor;
3182 };
3183 _.htmlTemplate = '<span class="mq-math-mode">&0</span>';
3184 _.createBlocks = function() {
3185 super_.createBlocks.call(this);
3186
3187 this.ends[L].cursor = this.cursor;
3188 this.ends[L].write = function(cursor, ch) {
3189 if (ch !== '$')
3190 MathBlock.prototype.write.call(this, cursor, ch);
3191 else if (this.isEmpty()) {
3192 cursor.insRightOf(this.parent);
3193 this.parent.deleteTowards(dir, cursor);
3194 VanillaSymbol('\\$','$').createLeftOf(cursor.show());
3195 }
3196 else if (!cursor[R])
3197 cursor.insRightOf(this.parent);
3198 else if (!cursor[L])
3199 cursor.insLeftOf(this.parent);
3200 else
3201 MathBlock.prototype.write.call(this, cursor, ch);
3202 };
3203 };
3204 _.latex = function() {
3205 return '$' + this.ends[L].latex() + '$';
3206 };
3207});
3208
3209var RootTextBlock = P(RootMathBlock, function(_, super_) {
3210 _.keystroke = function(key) {
3211 if (key === 'Spacebar' || key === 'Shift-Spacebar') return;
3212 return super_.keystroke.apply(this, arguments);
3213 };
3214 _.write = function(cursor, ch) {
3215 cursor.show().deleteSelection();
3216 if (ch === '$')
3217 RootMathCommand(cursor).createLeftOf(cursor);
3218 else {
3219 var html;
3220 if (ch === '<') html = '<';
3221 else if (ch === '>') html = '>';
3222 VanillaSymbol(ch, html).createLeftOf(cursor);
3223 }
3224 };
3225});
3226API.TextField = function(APIClasses) {
3227 return P(APIClasses.EditableField, function(_, super_) {
3228 this.RootBlock = RootTextBlock;
3229 _.__mathquillify = function() {
3230 return super_.__mathquillify.call(this, 'mq-editable-field mq-text-mode');
3231 };
3232 _.latex = function(latex) {
3233 if (arguments.length > 0) {
3234 this.__controller.renderLatexText(latex);
3235 if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
3236 return this;
3237 }
3238 return this.__controller.exportLatex();
3239 };
3240 });
3241};
3242/************************************
3243 * Symbols for Advanced Mathematics
3244 ***********************************/
3245
3246LatexCmds.notin =
3247LatexCmds.cong =
3248LatexCmds.equiv =
3249LatexCmds.oplus =
3250LatexCmds.otimes = P(BinaryOperator, function(_, super_) {
3251 _.init = function(latex) {
3252 super_.init.call(this, '\\'+latex+' ', '&'+latex+';');
3253 };
3254});
3255
3256LatexCmds['\u2260'] = LatexCmds.ne = LatexCmds.neq = bind(BinaryOperator,'\\ne ','≠');
3257
3258LatexCmds.ast = LatexCmds.star = LatexCmds.loast = LatexCmds.lowast =
3259 bind(BinaryOperator,'\\ast ','∗');
3260 //case 'there4 = // a special exception for this one, perhaps?
3261LatexCmds.therefor = LatexCmds.therefore =
3262 bind(BinaryOperator,'\\therefore ','∴');
3263
3264LatexCmds.cuz = // l33t
3265LatexCmds.because = bind(BinaryOperator,'\\because ','∵');
3266
3267LatexCmds.prop = LatexCmds.propto = bind(BinaryOperator,'\\propto ','∝');
3268
3269LatexCmds['\u2248'] = LatexCmds.asymp = LatexCmds.approx = bind(BinaryOperator,'\\approx ','≈');
3270
3271LatexCmds.isin = LatexCmds['in'] = bind(BinaryOperator,'\\in ','∈');
3272
3273LatexCmds.ni = LatexCmds.contains = bind(BinaryOperator,'\\ni ','∋');
3274
3275LatexCmds.notni = LatexCmds.niton = LatexCmds.notcontains = LatexCmds.doesnotcontain =
3276 bind(BinaryOperator,'\\not\\ni ','∌');
3277
3278LatexCmds.sub = LatexCmds.subset = bind(BinaryOperator,'\\subset ','⊂');
3279
3280LatexCmds.sup = LatexCmds.supset = LatexCmds.superset =
3281 bind(BinaryOperator,'\\supset ','⊃');
3282
3283LatexCmds.nsub = LatexCmds.notsub =
3284LatexCmds.nsubset = LatexCmds.notsubset =
3285 bind(BinaryOperator,'\\not\\subset ','⊄');
3286
3287LatexCmds.nsup = LatexCmds.notsup =
3288LatexCmds.nsupset = LatexCmds.notsupset =
3289LatexCmds.nsuperset = LatexCmds.notsuperset =
3290 bind(BinaryOperator,'\\not\\supset ','⊅');
3291
3292LatexCmds.sube = LatexCmds.subeq = LatexCmds.subsete = LatexCmds.subseteq =
3293 bind(BinaryOperator,'\\subseteq ','⊆');
3294
3295LatexCmds.supe = LatexCmds.supeq =
3296LatexCmds.supsete = LatexCmds.supseteq =
3297LatexCmds.supersete = LatexCmds.superseteq =
3298 bind(BinaryOperator,'\\supseteq ','⊇');
3299
3300LatexCmds.nsube = LatexCmds.nsubeq =
3301LatexCmds.notsube = LatexCmds.notsubeq =
3302LatexCmds.nsubsete = LatexCmds.nsubseteq =
3303LatexCmds.notsubsete = LatexCmds.notsubseteq =
3304 bind(BinaryOperator,'\\not\\subseteq ','⊈');
3305
3306LatexCmds.nsupe = LatexCmds.nsupeq =
3307LatexCmds.notsupe = LatexCmds.notsupeq =
3308LatexCmds.nsupsete = LatexCmds.nsupseteq =
3309LatexCmds.notsupsete = LatexCmds.notsupseteq =
3310LatexCmds.nsupersete = LatexCmds.nsuperseteq =
3311LatexCmds.notsupersete = LatexCmds.notsuperseteq =
3312 bind(BinaryOperator,'\\not\\supseteq ','⊉');
3313
3314
3315//the canonical sets of numbers
3316LatexCmds.N = LatexCmds.naturals = LatexCmds.Naturals =
3317 bind(VanillaSymbol,'\\mathbb{N}','ℕ');
3318
3319LatexCmds.P =
3320LatexCmds.primes = LatexCmds.Primes =
3321LatexCmds.projective = LatexCmds.Projective =
3322LatexCmds.probability = LatexCmds.Probability =
3323 bind(VanillaSymbol,'\\mathbb{P}','ℙ');
3324
3325LatexCmds.Z = LatexCmds.integers = LatexCmds.Integers =
3326 bind(VanillaSymbol,'\\mathbb{Z}','ℤ');
3327
3328LatexCmds.Q = LatexCmds.rationals = LatexCmds.Rationals =
3329 bind(VanillaSymbol,'\\mathbb{Q}','ℚ');
3330
3331LatexCmds.R = LatexCmds.reals = LatexCmds.Reals =
3332 bind(VanillaSymbol,'\\mathbb{R}','ℝ');
3333
3334LatexCmds.C =
3335LatexCmds.complex = LatexCmds.Complex =
3336LatexCmds.complexes = LatexCmds.Complexes =
3337LatexCmds.complexplane = LatexCmds.Complexplane = LatexCmds.ComplexPlane =
3338 bind(VanillaSymbol,'\\mathbb{C}','ℂ');
3339
3340LatexCmds.H = LatexCmds.Hamiltonian = LatexCmds.quaternions = LatexCmds.Quaternions =
3341 bind(VanillaSymbol,'\\mathbb{H}','ℍ');
3342
3343//spacing
3344LatexCmds.quad = LatexCmds.emsp = bind(VanillaSymbol,'\\quad ',' ');
3345LatexCmds.qquad = bind(VanillaSymbol,'\\qquad ',' ');
3346/* spacing special characters, gonna have to implement this in LatexCommandInput::onText somehow
3347case ',':
3348 return VanillaSymbol('\\, ',' ');
3349case ':':
3350 return VanillaSymbol('\\: ',' ');
3351case ';':
3352 return VanillaSymbol('\\; ',' ');
3353case '!':
3354 return Symbol('\\! ','<span style="margin-right:-.2em"></span>');
3355*/
3356
3357//binary operators
3358LatexCmds.diamond = bind(VanillaSymbol, '\\diamond ', '◇');
3359LatexCmds.bigtriangleup = bind(VanillaSymbol, '\\bigtriangleup ', '△');
3360LatexCmds.ominus = bind(VanillaSymbol, '\\ominus ', '⊖');
3361LatexCmds.uplus = bind(VanillaSymbol, '\\uplus ', '⊎');
3362LatexCmds.bigtriangledown = bind(VanillaSymbol, '\\bigtriangledown ', '▽');
3363LatexCmds.sqcap = bind(VanillaSymbol, '\\sqcap ', '⊓');
3364LatexCmds.triangleleft = bind(VanillaSymbol, '\\triangleleft ', '⊲');
3365LatexCmds.sqcup = bind(VanillaSymbol, '\\sqcup ', '⊔');
3366LatexCmds.triangleright = bind(VanillaSymbol, '\\triangleright ', '⊳');
3367//circledot is not a not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
3368LatexCmds.odot = LatexCmds.circledot = bind(VanillaSymbol, '\\odot ', '⊙');
3369LatexCmds.bigcirc = bind(VanillaSymbol, '\\bigcirc ', '◯');
3370LatexCmds.dagger = bind(VanillaSymbol, '\\dagger ', '†');
3371LatexCmds.ddagger = bind(VanillaSymbol, '\\ddagger ', '‡');
3372LatexCmds.wr = bind(VanillaSymbol, '\\wr ', '≀');
3373LatexCmds.amalg = bind(VanillaSymbol, '\\amalg ', '∐');
3374
3375//relationship symbols
3376LatexCmds.models = bind(VanillaSymbol, '\\models ', '⊨');
3377LatexCmds.prec = bind(VanillaSymbol, '\\prec ', '≺');
3378LatexCmds.succ = bind(VanillaSymbol, '\\succ ', '≻');
3379LatexCmds.preceq = bind(VanillaSymbol, '\\preceq ', '≼');
3380LatexCmds.succeq = bind(VanillaSymbol, '\\succeq ', '≽');
3381LatexCmds.simeq = bind(VanillaSymbol, '\\simeq ', '≃');
3382LatexCmds.mid = bind(VanillaSymbol, '\\mid ', '∣');
3383LatexCmds.ll = bind(VanillaSymbol, '\\ll ', '≪');
3384LatexCmds.gg = bind(VanillaSymbol, '\\gg ', '≫');
3385LatexCmds.parallel = bind(VanillaSymbol, '\\parallel ', '∥');
3386LatexCmds.nparallel = bind(VanillaSymbol, '\\nparallel ', '∦');
3387LatexCmds.bowtie = bind(VanillaSymbol, '\\bowtie ', '⋈');
3388LatexCmds.sqsubset = bind(VanillaSymbol, '\\sqsubset ', '⊏');
3389LatexCmds.sqsupset = bind(VanillaSymbol, '\\sqsupset ', '⊐');
3390LatexCmds.smile = bind(VanillaSymbol, '\\smile ', '⌣');
3391LatexCmds.sqsubseteq = bind(VanillaSymbol, '\\sqsubseteq ', '⊑');
3392LatexCmds.sqsupseteq = bind(VanillaSymbol, '\\sqsupseteq ', '⊒');
3393LatexCmds.doteq = bind(VanillaSymbol, '\\doteq ', '≐');
3394LatexCmds.frown = bind(VanillaSymbol, '\\frown ', '⌢');
3395LatexCmds.vdash = bind(VanillaSymbol, '\\vdash ', '⊦');
3396LatexCmds.dashv = bind(VanillaSymbol, '\\dashv ', '⊣');
3397LatexCmds.nless = bind(VanillaSymbol, '\\nless ', '≮');
3398LatexCmds.ngtr = bind(VanillaSymbol, '\\ngtr ', '≯');
3399
3400//arrows
3401LatexCmds.longleftarrow = bind(VanillaSymbol, '\\longleftarrow ', '←');
3402LatexCmds.longrightarrow = bind(VanillaSymbol, '\\longrightarrow ', '→');
3403LatexCmds.Longleftarrow = bind(VanillaSymbol, '\\Longleftarrow ', '⇐');
3404LatexCmds.Longrightarrow = bind(VanillaSymbol, '\\Longrightarrow ', '⇒');
3405LatexCmds.longleftrightarrow = bind(VanillaSymbol, '\\longleftrightarrow ', '↔');
3406LatexCmds.updownarrow = bind(VanillaSymbol, '\\updownarrow ', '↕');
3407LatexCmds.Longleftrightarrow = bind(VanillaSymbol, '\\Longleftrightarrow ', '⇔');
3408LatexCmds.Updownarrow = bind(VanillaSymbol, '\\Updownarrow ', '⇕');
3409LatexCmds.mapsto = bind(VanillaSymbol, '\\mapsto ', '↦');
3410LatexCmds.nearrow = bind(VanillaSymbol, '\\nearrow ', '↗');
3411LatexCmds.hookleftarrow = bind(VanillaSymbol, '\\hookleftarrow ', '↩');
3412LatexCmds.hookrightarrow = bind(VanillaSymbol, '\\hookrightarrow ', '↪');
3413LatexCmds.searrow = bind(VanillaSymbol, '\\searrow ', '↘');
3414LatexCmds.leftharpoonup = bind(VanillaSymbol, '\\leftharpoonup ', '↼');
3415LatexCmds.rightharpoonup = bind(VanillaSymbol, '\\rightharpoonup ', '⇀');
3416LatexCmds.swarrow = bind(VanillaSymbol, '\\swarrow ', '↙');
3417LatexCmds.leftharpoondown = bind(VanillaSymbol, '\\leftharpoondown ', '↽');
3418LatexCmds.rightharpoondown = bind(VanillaSymbol, '\\rightharpoondown ', '⇁');
3419LatexCmds.nwarrow = bind(VanillaSymbol, '\\nwarrow ', '↖');
3420
3421//Misc
3422LatexCmds.ldots = bind(VanillaSymbol, '\\ldots ', '…');
3423LatexCmds.cdots = bind(VanillaSymbol, '\\cdots ', '⋯');
3424LatexCmds.vdots = bind(VanillaSymbol, '\\vdots ', '⋮');
3425LatexCmds.ddots = bind(VanillaSymbol, '\\ddots ', '⋱');
3426LatexCmds.surd = bind(VanillaSymbol, '\\surd ', '√');
3427LatexCmds.triangle = bind(VanillaSymbol, '\\triangle ', '△');
3428LatexCmds.ell = bind(VanillaSymbol, '\\ell ', 'ℓ');
3429LatexCmds.top = bind(VanillaSymbol, '\\top ', '⊤');
3430LatexCmds.flat = bind(VanillaSymbol, '\\flat ', '♭');
3431LatexCmds.natural = bind(VanillaSymbol, '\\natural ', '♮');
3432LatexCmds.sharp = bind(VanillaSymbol, '\\sharp ', '♯');
3433LatexCmds.wp = bind(VanillaSymbol, '\\wp ', '℘');
3434LatexCmds.bot = bind(VanillaSymbol, '\\bot ', '⊥');
3435LatexCmds.clubsuit = bind(VanillaSymbol, '\\clubsuit ', '♣');
3436LatexCmds.diamondsuit = bind(VanillaSymbol, '\\diamondsuit ', '♢');
3437LatexCmds.heartsuit = bind(VanillaSymbol, '\\heartsuit ', '♡');
3438LatexCmds.spadesuit = bind(VanillaSymbol, '\\spadesuit ', '♠');
3439//not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
3440LatexCmds.parallelogram = bind(VanillaSymbol, '\\parallelogram ', '▱');
3441LatexCmds.square = bind(VanillaSymbol, '\\square ', '⬜');
3442
3443//variable-sized
3444LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '∮');
3445LatexCmds.bigcap = bind(VanillaSymbol, '\\bigcap ', '∩');
3446LatexCmds.bigcup = bind(VanillaSymbol, '\\bigcup ', '∪');
3447LatexCmds.bigsqcup = bind(VanillaSymbol, '\\bigsqcup ', '⊔');
3448LatexCmds.bigvee = bind(VanillaSymbol, '\\bigvee ', '∨');
3449LatexCmds.bigwedge = bind(VanillaSymbol, '\\bigwedge ', '∧');
3450LatexCmds.bigodot = bind(VanillaSymbol, '\\bigodot ', '⊙');
3451LatexCmds.bigotimes = bind(VanillaSymbol, '\\bigotimes ', '⊗');
3452LatexCmds.bigoplus = bind(VanillaSymbol, '\\bigoplus ', '⊕');
3453LatexCmds.biguplus = bind(VanillaSymbol, '\\biguplus ', '⊎');
3454
3455//delimiters
3456LatexCmds.lfloor = bind(VanillaSymbol, '\\lfloor ', '⌊');
3457LatexCmds.rfloor = bind(VanillaSymbol, '\\rfloor ', '⌋');
3458LatexCmds.lceil = bind(VanillaSymbol, '\\lceil ', '⌈');
3459LatexCmds.rceil = bind(VanillaSymbol, '\\rceil ', '⌉');
3460LatexCmds.opencurlybrace = LatexCmds.lbrace = bind(VanillaSymbol, '\\lbrace ', '{');
3461LatexCmds.closecurlybrace = LatexCmds.rbrace = bind(VanillaSymbol, '\\rbrace ', '}');
3462LatexCmds.lbrack = bind(VanillaSymbol, '[');
3463LatexCmds.rbrack = bind(VanillaSymbol, ']');
3464
3465//various symbols
3466LatexCmds.slash = bind(VanillaSymbol, '/');
3467LatexCmds.vert = bind(VanillaSymbol,'|');
3468LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','⊥');
3469LatexCmds.nabla = LatexCmds.del = bind(VanillaSymbol,'\\nabla ','∇');
3470LatexCmds.hbar = bind(VanillaSymbol,'\\hbar ','ℏ');
3471
3472LatexCmds.AA = LatexCmds.Angstrom = LatexCmds.angstrom =
3473 bind(VanillaSymbol,'\\text\\AA ','Å');
3474
3475LatexCmds.ring = LatexCmds.circ = LatexCmds.circle =
3476 bind(VanillaSymbol,'\\circ ','∘');
3477
3478LatexCmds.bull = LatexCmds.bullet = bind(VanillaSymbol,'\\bullet ','•');
3479
3480LatexCmds.setminus = LatexCmds.smallsetminus =
3481 bind(VanillaSymbol,'\\setminus ','∖');
3482
3483LatexCmds.not = //bind(Symbol,'\\not ','<span class="not">/</span>');
3484LatexCmds['\u00ac'] = LatexCmds.neg = bind(VanillaSymbol,'\\neg ','¬');
3485
3486LatexCmds['\u2026'] = LatexCmds.dots = LatexCmds.ellip = LatexCmds.hellip =
3487LatexCmds.ellipsis = LatexCmds.hellipsis =
3488 bind(VanillaSymbol,'\\dots ','…');
3489
3490LatexCmds.converges =
3491LatexCmds.darr = LatexCmds.dnarr = LatexCmds.dnarrow = LatexCmds.downarrow =
3492 bind(VanillaSymbol,'\\downarrow ','↓');
3493
3494LatexCmds.dArr = LatexCmds.dnArr = LatexCmds.dnArrow = LatexCmds.Downarrow =
3495 bind(VanillaSymbol,'\\Downarrow ','⇓');
3496
3497LatexCmds.diverges = LatexCmds.uarr = LatexCmds.uparrow =
3498 bind(VanillaSymbol,'\\uparrow ','↑');
3499
3500LatexCmds.uArr = LatexCmds.Uparrow = bind(VanillaSymbol,'\\Uparrow ','⇑');
3501
3502LatexCmds.to = bind(BinaryOperator,'\\to ','→');
3503
3504LatexCmds.rarr = LatexCmds.rightarrow = bind(VanillaSymbol,'\\rightarrow ','→');
3505
3506LatexCmds.implies = bind(BinaryOperator,'\\Rightarrow ','⇒');
3507
3508LatexCmds.rArr = LatexCmds.Rightarrow = bind(VanillaSymbol,'\\Rightarrow ','⇒');
3509
3510LatexCmds.gets = bind(BinaryOperator,'\\gets ','←');
3511
3512LatexCmds.larr = LatexCmds.leftarrow = bind(VanillaSymbol,'\\leftarrow ','←');
3513
3514LatexCmds.impliedby = bind(BinaryOperator,'\\Leftarrow ','⇐');
3515
3516LatexCmds.lArr = LatexCmds.Leftarrow = bind(VanillaSymbol,'\\Leftarrow ','⇐');
3517
3518LatexCmds.harr = LatexCmds.lrarr = LatexCmds.leftrightarrow =
3519 bind(VanillaSymbol,'\\leftrightarrow ','↔');
3520
3521LatexCmds.iff = bind(BinaryOperator,'\\Leftrightarrow ','⇔');
3522
3523LatexCmds.hArr = LatexCmds.lrArr = LatexCmds.Leftrightarrow =
3524 bind(VanillaSymbol,'\\Leftrightarrow ','⇔');
3525
3526LatexCmds.Re = LatexCmds.Real = LatexCmds.real = bind(VanillaSymbol,'\\Re ','ℜ');
3527
3528LatexCmds.Im = LatexCmds.imag =
3529LatexCmds.image = LatexCmds.imagin = LatexCmds.imaginary = LatexCmds.Imaginary =
3530 bind(VanillaSymbol,'\\Im ','ℑ');
3531
3532LatexCmds.part = LatexCmds.partial = bind(VanillaSymbol,'\\partial ','∂');
3533
3534LatexCmds.infty = LatexCmds.infin = LatexCmds.infinity =
3535 bind(VanillaSymbol,'\\infty ','∞');
3536
3537LatexCmds.alef = LatexCmds.alefsym = LatexCmds.aleph = LatexCmds.alephsym =
3538 bind(VanillaSymbol,'\\aleph ','ℵ');
3539
3540LatexCmds.xist = //LOL
3541LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
3542 bind(VanillaSymbol,'\\exists ','∃');
3543
3544LatexCmds.nexists = LatexCmds.nexist =
3545 bind(VanillaSymbol, '\\nexists ', '∄');
3546
3547LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
3548 bind(VanillaSymbol,'\\wedge ','∧');
3549
3550LatexCmds.or = LatexCmds.lor = LatexCmds.vee = bind(VanillaSymbol,'\\vee ','∨');
3551
3552LatexCmds.o = LatexCmds.O =
3553LatexCmds.empty = LatexCmds.emptyset =
3554LatexCmds.oslash = LatexCmds.Oslash =
3555LatexCmds.nothing = LatexCmds.varnothing =
3556 bind(BinaryOperator,'\\varnothing ','∅');
3557
3558LatexCmds.cup = LatexCmds.union = bind(BinaryOperator,'\\cup ','∪');
3559
3560LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection =
3561 bind(BinaryOperator,'\\cap ','∩');
3562
3563// FIXME: the correct LaTeX would be ^\circ but we can't parse that
3564LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'\\degree ','°');
3565
3566LatexCmds.ang = LatexCmds.angle = bind(VanillaSymbol,'\\angle ','∠');
3567LatexCmds.measuredangle = bind(VanillaSymbol,'\\measuredangle ','∡');
3568/*********************************
3569 * Symbols for Basic Mathematics
3570 ********************************/
3571
3572var Digit = P(VanillaSymbol, function(_, super_) {
3573 _.createLeftOf = function(cursor) {
3574 if (cursor.options.autoSubscriptNumerals
3575 && cursor.parent !== cursor.parent.parent.sub
3576 && ((cursor[L] instanceof Variable && cursor[L].isItalic !== false)
3577 || (cursor[L] instanceof SupSub
3578 && cursor[L][L] instanceof Variable
3579 && cursor[L][L].isItalic !== false))) {
3580 LatexCmds._().createLeftOf(cursor);
3581 super_.createLeftOf.call(this, cursor);
3582 cursor.insRightOf(cursor.parent.parent);
3583 }
3584 else super_.createLeftOf.call(this, cursor);
3585 };
3586});
3587
3588var Variable = P(Symbol, function(_, super_) {
3589 _.init = function(ch, html) {
3590 super_.init.call(this, ch, '<var>'+(html || ch)+'</var>');
3591 };
3592 _.text = function() {
3593 var text = this.ctrlSeq;
3594 if (this[L] && !(this[L] instanceof Variable)
3595 && !(this[L] instanceof BinaryOperator)
3596 && this[L].ctrlSeq !== "\\ ")
3597 text = '*' + text;
3598 if (this[R] && !(this[R] instanceof BinaryOperator)
3599 && !(this[R] instanceof SupSub))
3600 text += '*';
3601 return text;
3602 };
3603});
3604
3605Options.p.autoCommands = { _maxLength: 0 };
3606optionProcessors.autoCommands = function(cmds) {
3607 if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
3608 throw '"'+cmds+'" not a space-delimited list of only letters';
3609 }
3610 var list = cmds.split(' '), dict = {}, maxLength = 0;
3611 for (var i = 0; i < list.length; i += 1) {
3612 var cmd = list[i];
3613 if (cmd.length < 2) {
3614 throw 'autocommand "'+cmd+'" not minimum length of 2';
3615 }
3616 if (LatexCmds[cmd] === OperatorName) {
3617 throw '"' + cmd + '" is a built-in operator name';
3618 }
3619 dict[cmd] = 1;
3620 maxLength = max(maxLength, cmd.length);
3621 }
3622 dict._maxLength = maxLength;
3623 return dict;
3624};
3625
3626var Letter = P(Variable, function(_, super_) {
3627 _.init = function(ch) { return super_.init.call(this, this.letter = ch); };
3628 _.createLeftOf = function(cursor) {
3629 super_.createLeftOf.apply(this, arguments);
3630 var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
3631 if (maxLength > 0) {
3632 // want longest possible autocommand, so join together longest
3633 // sequence of letters
3634 var str = '', l = this, i = 0;
3635 // FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name
3636 while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) {
3637 str = l.letter + str, l = l[L], i += 1;
3638 }
3639 // check for an autocommand, going thru substrings longest to shortest
3640 while (str.length) {
3641 if (autoCmds.hasOwnProperty(str)) {
3642 for (var i = 1, l = this; i < str.length; i += 1, l = l[L]);
3643 Fragment(l, this).remove();
3644 cursor[L] = l[L];
3645 return LatexCmds[str](str).createLeftOf(cursor);
3646 }
3647 str = str.slice(1);
3648 }
3649 }
3650 };
3651 _.italicize = function(bool) {
3652 this.isItalic = bool;
3653 this.jQ.toggleClass('mq-operator-name', !bool);
3654 return this;
3655 };
3656 _.finalizeTree = _.siblingDeleted = _.siblingCreated = function(opts, dir) {
3657 // don't auto-un-italicize if the sibling to my right changed (dir === R or
3658 // undefined) and it's now a Letter, it will un-italicize everyone
3659 if (dir !== L && this[R] instanceof Letter) return;
3660 this.autoUnItalicize(opts);
3661 };
3662 _.autoUnItalicize = function(opts) {
3663 var autoOps = opts.autoOperatorNames;
3664 if (autoOps._maxLength === 0) return;
3665 // want longest possible operator names, so join together entire contiguous
3666 // sequence of letters
3667 var str = this.letter;
3668 for (var l = this[L]; l instanceof Letter; l = l[L]) str = l.letter + str;
3669 for (var r = this[R]; r instanceof Letter; r = r[R]) str += r.letter;
3670
3671 // removeClass and delete flags from all letters before figuring out
3672 // which, if any, are part of an operator name
3673 Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
3674 el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub');
3675 el.ctrlSeq = el.letter;
3676 });
3677
3678 // check for operator names: at each position from left to right, check
3679 // substrings from longest to shortest
3680 outer: for (var i = 0, first = l[R] || this.parent.ends[L]; i < str.length; i += 1, first = first[R]) {
3681 for (var len = min(autoOps._maxLength, str.length - i); len > 0; len -= 1) {
3682 var word = str.slice(i, i + len);
3683 if (autoOps.hasOwnProperty(word)) {
3684 for (var j = 0, letter = first; j < len; j += 1, letter = letter[R]) {
3685 letter.italicize(false);
3686 var last = letter;
3687 }
3688
3689 var isBuiltIn = BuiltInOpNames.hasOwnProperty(word);
3690 first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
3691 last.ctrlSeq += (isBuiltIn ? ' ' : '}');
3692 if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
3693 if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first');
3694 if (!shouldOmitPadding(last[R])) {
3695 if (last[R] instanceof SupSub) {
3696 var supsub = last[R]; // XXX monkey-patching, but what's the right thing here?
3697 // Have operatorname-specific code in SupSub? A CSS-like language to style the
3698 // math tree, but which ignores cursor and selection (which CSS can't)?
3699 var respace = supsub.siblingCreated = supsub.siblingDeleted = function() {
3700 supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket));
3701 };
3702 respace();
3703 }
3704 else {
3705 last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket));
3706 }
3707 }
3708
3709 i += len - 1;
3710 first = last;
3711 continue outer;
3712 }
3713 }
3714 }
3715 };
3716 function shouldOmitPadding(node) {
3717 // omit padding if no node, or if node already has padding (to avoid double-padding)
3718 return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation);
3719 }
3720});
3721var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
3722 // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc
3723 // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
3724 // and 'arsinh', which must be exported as \operatorname{hcf} and
3725 // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
3726 // \varlimsup are not supported
3727var AutoOpNames = Options.p.autoOperatorNames = { _maxLength: 9 }; // the set
3728 // of operator names that MathQuill auto-unitalicizes by default; overridable
3729var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 };
3730(function() {
3731 var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup'
3732 + ' limsup liminf injlim projlim Pr').split(' ');
3733 for (var i = 0; i < mostOps.length; i += 1) {
3734 BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1;
3735 }
3736
3737 var builtInTrigs = // why coth but not sech and csch, LaTeX?
3738 'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' ');
3739 for (var i = 0; i < builtInTrigs.length; i += 1) {
3740 BuiltInOpNames[builtInTrigs[i]] = 1;
3741 }
3742
3743 var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' ');
3744 for (var i = 0; i < autoTrigs.length; i += 1) {
3745 AutoOpNames[autoTrigs[i]] =
3746 AutoOpNames['arc'+autoTrigs[i]] =
3747 AutoOpNames[autoTrigs[i]+'h'] =
3748 AutoOpNames['ar'+autoTrigs[i]+'h'] =
3749 AutoOpNames['arc'+autoTrigs[i]+'h'] = 1;
3750 }
3751
3752 // compat with some of the nonstandard LaTeX exported by MathQuill
3753 // before #247. None of these are real LaTeX commands so, seems safe
3754 var moreNonstandardOps = 'gcf hcf lcm proj span'.split(' ');
3755 for (var i = 0; i < moreNonstandardOps.length; i += 1) {
3756 AutoOpNames[moreNonstandardOps[i]] = 1;
3757 }
3758}());
3759optionProcessors.autoOperatorNames = function(cmds) {
3760 if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) {
3761 throw '"'+cmds+'" not a space-delimited list of only letters';
3762 }
3763 var list = cmds.split(' '), dict = {}, maxLength = 0;
3764 for (var i = 0; i < list.length; i += 1) {
3765 var cmd = list[i];
3766 if (cmd.length < 2) {
3767 throw '"'+cmd+'" not minimum length of 2';
3768 }
3769 dict[cmd] = 1;
3770 maxLength = max(maxLength, cmd.length);
3771 }
3772 dict._maxLength = maxLength;
3773 return dict;
3774};
3775var OperatorName = P(Symbol, function(_, super_) {
3776 _.init = function(fn) { this.ctrlSeq = fn; };
3777 _.createLeftOf = function(cursor) {
3778 var fn = this.ctrlSeq;
3779 for (var i = 0; i < fn.length; i += 1) {
3780 Letter(fn.charAt(i)).createLeftOf(cursor);
3781 }
3782 };
3783 _.parser = function() {
3784 var fn = this.ctrlSeq;
3785 var block = MathBlock();
3786 for (var i = 0; i < fn.length; i += 1) {
3787 Letter(fn.charAt(i)).adopt(block, block.ends[R], 0);
3788 }
3789 return Parser.succeed(block.children());
3790 };
3791});
3792for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) {
3793 LatexCmds[fn] = OperatorName;
3794}
3795LatexCmds.operatorname = P(MathCommand, function(_) {
3796 _.createLeftOf = noop;
3797 _.numBlocks = function() { return 1; };
3798 _.parser = function() {
3799 return latexMathParser.block.map(function(b) { return b.children(); });
3800 };
3801});
3802
3803LatexCmds.f = P(Letter, function(_, super_) {
3804 _.init = function() {
3805 Symbol.p.init.call(this, this.letter = 'f', '<var class="mq-f">f</var>');
3806 };
3807 _.italicize = function(bool) {
3808 this.jQ.html('f').toggleClass('mq-f', bool);
3809 return super_.italicize.apply(this, arguments);
3810 };
3811});
3812
3813// VanillaSymbol's
3814LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', ' ');
3815
3816LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '′');
3817
3818LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
3819if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash;
3820
3821LatexCmds.$ = bind(VanillaSymbol, '\\$', '$');
3822
3823// does not use Symbola font
3824var NonSymbolaSymbol = P(Symbol, function(_, super_) {
3825 _.init = function(ch, html) {
3826 super_.init.call(this, ch, '<span class="mq-nonSymbola">'+(html || ch)+'</span>');
3827 };
3828});
3829
3830LatexCmds['@'] = NonSymbolaSymbol;
3831LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&');
3832LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
3833
3834//the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html
3835
3836//lowercase Greek letter variables
3837LatexCmds.alpha =
3838LatexCmds.beta =
3839LatexCmds.gamma =
3840LatexCmds.delta =
3841LatexCmds.zeta =
3842LatexCmds.eta =
3843LatexCmds.theta =
3844LatexCmds.iota =
3845LatexCmds.kappa =
3846LatexCmds.mu =
3847LatexCmds.nu =
3848LatexCmds.xi =
3849LatexCmds.rho =
3850LatexCmds.sigma =
3851LatexCmds.tau =
3852LatexCmds.chi =
3853LatexCmds.psi =
3854LatexCmds.omega = P(Variable, function(_, super_) {
3855 _.init = function(latex) {
3856 super_.init.call(this,'\\'+latex+' ','&'+latex+';');
3857 };
3858});
3859
3860//why can't anybody FUCKING agree on these
3861LatexCmds.phi = //W3C or Unicode?
3862 bind(Variable,'\\phi ','ϕ');
3863
3864LatexCmds.phiv = //Elsevier and 9573-13
3865LatexCmds.varphi = //AMS and LaTeX
3866 bind(Variable,'\\varphi ','φ');
3867
3868LatexCmds.epsilon = //W3C or Unicode?
3869 bind(Variable,'\\epsilon ','ϵ');
3870
3871LatexCmds.epsiv = //Elsevier and 9573-13
3872LatexCmds.varepsilon = //AMS and LaTeX
3873 bind(Variable,'\\varepsilon ','ε');
3874
3875LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13
3876LatexCmds.varpi = //AMS and LaTeX
3877 bind(Variable,'\\varpi ','ϖ');
3878
3879LatexCmds.sigmaf = //W3C/Unicode
3880LatexCmds.sigmav = //Elsevier
3881LatexCmds.varsigma = //LaTeX
3882 bind(Variable,'\\varsigma ','ς');
3883
3884LatexCmds.thetav = //Elsevier and 9573-13
3885LatexCmds.vartheta = //AMS and LaTeX
3886LatexCmds.thetasym = //W3C/Unicode
3887 bind(Variable,'\\vartheta ','ϑ');
3888
3889LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode
3890LatexCmds.upsi = //Elsevier and 9573-13
3891 bind(Variable,'\\upsilon ','υ');
3892
3893//these aren't even mentioned in the HTML character entity references
3894LatexCmds.gammad = //Elsevier
3895LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above)
3896LatexCmds.digamma = //LaTeX
3897 bind(Variable,'\\digamma ','ϝ');
3898
3899LatexCmds.kappav = //Elsevier
3900LatexCmds.varkappa = //AMS and LaTeX
3901 bind(Variable,'\\varkappa ','ϰ');
3902
3903LatexCmds.rhov = //Elsevier and 9573-13
3904LatexCmds.varrho = //AMS and LaTeX
3905 bind(Variable,'\\varrho ','ϱ');
3906
3907//Greek constants, look best in non-italicized Times New Roman
3908LatexCmds.pi = LatexCmds['\u03c0'] = bind(NonSymbolaSymbol,'\\pi ','π');
3909LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','λ');
3910
3911//uppercase greek letters
3912
3913LatexCmds.Upsilon = //LaTeX
3914LatexCmds.Upsi = //Elsevier and 9573-13
3915LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
3916LatexCmds.Upsih = //'cos it makes sense to me
3917 bind(Symbol,'\\Upsilon ','<var style="font-family: serif">ϒ</var>'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :(
3918
3919//other symbols with the same LaTeX command and HTML character entity reference
3920LatexCmds.Gamma =
3921LatexCmds.Delta =
3922LatexCmds.Theta =
3923LatexCmds.Lambda =
3924LatexCmds.Xi =
3925LatexCmds.Pi =
3926LatexCmds.Sigma =
3927LatexCmds.Phi =
3928LatexCmds.Psi =
3929LatexCmds.Omega =
3930LatexCmds.forall = P(VanillaSymbol, function(_, super_) {
3931 _.init = function(latex) {
3932 super_.init.call(this,'\\'+latex+' ','&'+latex+';');
3933 };
3934});
3935
3936// symbols that aren't a single MathCommand, but are instead a whole
3937// Fragment. Creates the Fragment from a LaTeX string
3938var LatexFragment = P(MathCommand, function(_) {
3939 _.init = function(latex) { this.latex = latex; };
3940 _.createLeftOf = function(cursor) {
3941 var block = latexMathParser.parse(this.latex);
3942 block.children().adopt(cursor.parent, cursor[L], cursor[R]);
3943 cursor[L] = block.ends[R];
3944 block.jQize().insertBefore(cursor.jQ);
3945 block.finalizeInsert(cursor.options, cursor);
3946 if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L);
3947 if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R);
3948 cursor.parent.bubble('reflow');
3949 };
3950 _.parser = function() {
3951 var frag = latexMathParser.parse(this.latex).children();
3952 return Parser.succeed(frag);
3953 };
3954});
3955
3956// for what seems to me like [stupid reasons][1], Unicode provides
3957// subscripted and superscripted versions of all ten Arabic numerals,
3958// as well as [so-called "vulgar fractions"][2].
3959// Nobody really cares about most of them, but some of them actually
3960// predate Unicode, dating back to [ISO-8859-1][3], apparently also
3961// known as "Latin-1", which among other things [Windows-1252][4]
3962// largely coincides with, so Microsoft Word sometimes inserts them
3963// and they get copy-pasted into MathQuill.
3964//
3965// (Irrelevant but funny story: though not a superset of Latin-1 aka
3966// ISO-8859-1, Windows-1252 **is** a strict superset of the "closely
3967// related but distinct"[3] "ISO 8859-1" -- see the lack of a dash
3968// after "ISO"? Completely different character set, like elephants vs
3969// elephant seals, or "Zombies" vs "Zombie Redneck Torture Family".
3970// What kind of idiot would get them confused.
3971// People in fact got them confused so much, it was so common to
3972// mislabel Windows-1252 text as ISO-8859-1, that most modern web
3973// browsers and email clients treat the MIME charset of ISO-8859-1
3974// as actually Windows-1252, behavior now standard in the HTML5 spec.)
3975//
3976// [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_andsuper_scripts
3977// [2]: http://en.wikipedia.org/wiki/Number_Forms
3978// [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
3979// [4]: http://en.wikipedia.org/wiki/Windows-1252
3980LatexCmds['\u00b9'] = bind(LatexFragment, '^1');
3981LatexCmds['\u00b2'] = bind(LatexFragment, '^2');
3982LatexCmds['\u00b3'] = bind(LatexFragment, '^3');
3983LatexCmds['\u00bc'] = bind(LatexFragment, '\\frac14');
3984LatexCmds['\u00bd'] = bind(LatexFragment, '\\frac12');
3985LatexCmds['\u00be'] = bind(LatexFragment, '\\frac34');
3986
3987var PlusMinus = P(BinaryOperator, function(_) {
3988 _.init = VanillaSymbol.prototype.init;
3989
3990 _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
3991 if (dir === R) return; // ignore if sibling only changed on the right
3992 // If the left sibling is a binary operator or a separator (comma, semicolon, colon)
3993 // or an open bracket (open parenthesis, open square bracket)
3994 // consider the operator to be unary, otherwise binary
3995 this.jQ[0].className =
3996 (!this[L] || this[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(this[L].ctrlSeq) ? '' : 'mq-binary-operator');
3997 return this;
3998 };
3999});
4000
4001LatexCmds['+'] = bind(PlusMinus, '+', '+');
4002//yes, these are different dashes, I think one is an en dash and the other is a hyphen
4003LatexCmds['\u2013'] = LatexCmds['-'] = bind(PlusMinus, '-', '−');
4004LatexCmds['\u00b1'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus =
4005 bind(PlusMinus,'\\pm ','±');
4006LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus =
4007 bind(PlusMinus,'\\mp ','∓');
4008
4009CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
4010 bind(BinaryOperator, '\\cdot ', '·', '*');
4011//semantically should be ⋅, but · looks better
4012
4013var Inequality = P(BinaryOperator, function(_, super_) {
4014 _.init = function(data, strict) {
4015 this.data = data;
4016 this.strict = strict;
4017 var strictness = (strict ? 'Strict' : '');
4018 super_.init.call(this, data['ctrlSeq'+strictness], data['html'+strictness],
4019 data['text'+strictness]);
4020 };
4021 _.swap = function(strict) {
4022 this.strict = strict;
4023 var strictness = (strict ? 'Strict' : '');
4024 this.ctrlSeq = this.data['ctrlSeq'+strictness];
4025 this.jQ.html(this.data['html'+strictness]);
4026 this.textTemplate = [ this.data['text'+strictness] ];
4027 };
4028 _.deleteTowards = function(dir, cursor) {
4029 if (dir === L && !this.strict) {
4030 this.swap(true);
4031 this.bubble('reflow');
4032 return;
4033 }
4034 super_.deleteTowards.apply(this, arguments);
4035 };
4036});
4037
4038var less = { ctrlSeq: '\\le ', html: '≤', text: '\u2264',
4039 ctrlSeqStrict: '<', htmlStrict: '<', textStrict: '<' };
4040var greater = { ctrlSeq: '\\ge ', html: '≥', text: '\u2265',
4041 ctrlSeqStrict: '>', htmlStrict: '>', textStrict: '>' };
4042
4043LatexCmds['<'] = LatexCmds.lt = bind(Inequality, less, true);
4044LatexCmds['>'] = LatexCmds.gt = bind(Inequality, greater, true);
4045LatexCmds['\u2264'] = LatexCmds.le = LatexCmds.leq = bind(Inequality, less, false);
4046LatexCmds['\u2265'] = LatexCmds.ge = LatexCmds.geq = bind(Inequality, greater, false);
4047
4048var Equality = P(BinaryOperator, function(_, super_) {
4049 _.init = function() {
4050 super_.init.call(this, '=', '=');
4051 };
4052 _.createLeftOf = function(cursor) {
4053 if (cursor[L] instanceof Inequality && cursor[L].strict) {
4054 cursor[L].swap(false);
4055 cursor[L].bubble('reflow');
4056 return;
4057 }
4058 super_.createLeftOf.apply(this, arguments);
4059 };
4060});
4061LatexCmds['='] = Equality;
4062
4063LatexCmds['\u00d7'] = LatexCmds.times = bind(BinaryOperator, '\\times ', '×', '[x]');
4064
4065LatexCmds['\u00f7'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides =
4066 bind(BinaryOperator,'\\div ','÷', '[/]');
4067
4068CharCmds['~'] = LatexCmds.sim = bind(BinaryOperator, '\\sim ', '~', '~');
4069/***************************
4070 * Commands and Operators.
4071 **************************/
4072
4073var scale, // = function(jQ, x, y) { ... }
4074//will use a CSS 2D transform to scale the jQuery-wrapped HTML elements,
4075//or the filter matrix transform fallback for IE 5.5-8, or gracefully degrade to
4076//increasing the fontSize to match the vertical Y scaling factor.
4077
4078//ideas from http://github.com/louisremi/jquery.transform.js
4079//see also http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx
4080
4081 forceIERedraw = noop,
4082 div = document.createElement('div'),
4083 div_style = div.style,
4084 transformPropNames = {
4085 transform:1,
4086 WebkitTransform:1,
4087 MozTransform:1,
4088 OTransform:1,
4089 msTransform:1
4090 },
4091 transformPropName;
4092
4093for (var prop in transformPropNames) {
4094 if (prop in div_style) {
4095 transformPropName = prop;
4096 break;
4097 }
4098}
4099
4100if (transformPropName) {
4101 scale = function(jQ, x, y) {
4102 jQ.css(transformPropName, 'scale('+x+','+y+')');
4103 };
4104}
4105else if ('filter' in div_style) { //IE 6, 7, & 8 fallback, see https://github.com/laughinghan/mathquill/wiki/Transforms
4106 forceIERedraw = function(el){ el.className = el.className; };
4107 scale = function(jQ, x, y) { //NOTE: assumes y > x
4108 x /= (1+(y-1)/2);
4109 jQ.css('fontSize', y + 'em');
4110 if (!jQ.hasClass('mq-matrixed-container')) {
4111 jQ.addClass('mq-matrixed-container')
4112 .wrapInner('<span class="mq-matrixed"></span>');
4113 }
4114 var innerjQ = jQ.children()
4115 .css('filter', 'progid:DXImageTransform.Microsoft'
4116 + '.Matrix(M11=' + x + ",SizingMethod='auto expand')"
4117 );
4118 function calculateMarginRight() {
4119 jQ.css('marginRight', (innerjQ.width()-1)*(x-1)/x + 'px');
4120 }
4121 calculateMarginRight();
4122 var intervalId = setInterval(calculateMarginRight);
4123 $(window).load(function() {
4124 clearTimeout(intervalId);
4125 calculateMarginRight();
4126 });
4127 };
4128}
4129else {
4130 scale = function(jQ, x, y) {
4131 jQ.css('fontSize', y + 'em');
4132 };
4133}
4134
4135var Style = P(MathCommand, function(_, super_) {
4136 _.init = function(ctrlSeq, tagName, attrs) {
4137 super_.init.call(this, ctrlSeq, '<'+tagName+' '+attrs+'>&0</'+tagName+'>');
4138 };
4139});
4140
4141//fonts
4142LatexCmds.mathrm = bind(Style, '\\mathrm', 'span', 'class="mq-roman mq-font"');
4143LatexCmds.mathit = bind(Style, '\\mathit', 'i', 'class="mq-font"');
4144LatexCmds.mathbf = bind(Style, '\\mathbf', 'b', 'class="mq-font"');
4145LatexCmds.mathsf = bind(Style, '\\mathsf', 'span', 'class="mq-sans-serif mq-font"');
4146LatexCmds.mathtt = bind(Style, '\\mathtt', 'span', 'class="mq-monospace mq-font"');
4147//text-decoration
4148LatexCmds.underline = bind(Style, '\\underline', 'span', 'class="mq-non-leaf mq-underline"');
4149LatexCmds.overline = LatexCmds.bar = bind(Style, '\\overline', 'span', 'class="mq-non-leaf mq-overline"');
4150LatexCmds.overrightarrow = bind(Style, '\\overrightarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-right"');
4151LatexCmds.overleftarrow = bind(Style, '\\overleftarrow', 'span', 'class="mq-non-leaf mq-overarrow mq-arrow-left"');
4152
4153// `\textcolor{color}{math}` will apply a color to the given math content, where
4154// `color` is any valid CSS Color Value (see [SitePoint docs][] (recommended),
4155// [Mozilla docs][], or [W3C spec][]).
4156//
4157// [SitePoint docs]: http://reference.sitepoint.com/css/colorvalues
4158// [Mozilla docs]: https://developer.mozilla.org/en-US/docs/CSS/color_value#Values
4159// [W3C spec]: http://dev.w3.org/csswg/css3-color/#colorunits
4160var TextColor = LatexCmds.textcolor = P(MathCommand, function(_, super_) {
4161 _.setColor = function(color) {
4162 this.color = color;
4163 this.htmlTemplate =
4164 '<span class="mq-textcolor" style="color:' + color + '">&0</span>';
4165 };
4166 _.latex = function() {
4167 return '\\textcolor{' + this.color + '}{' + this.blocks[0].latex() + '}';
4168 };
4169 _.parser = function() {
4170 var self = this;
4171 var optWhitespace = Parser.optWhitespace;
4172 var string = Parser.string;
4173 var regex = Parser.regex;
4174
4175 return optWhitespace
4176 .then(string('{'))
4177 .then(regex(/^[#\w\s.,()%-]*/))
4178 .skip(string('}'))
4179 .then(function(color) {
4180 self.setColor(color);
4181 return super_.parser.call(self);
4182 })
4183 ;
4184 };
4185});
4186
4187// Very similar to the \textcolor command, but will add the given CSS class.
4188// Usage: \class{classname}{math}
4189// Note regex that whitelists valid CSS classname characters:
4190// https://github.com/mathquill/mathquill/pull/191#discussion_r4327442
4191var Class = LatexCmds['class'] = P(MathCommand, function(_, super_) {
4192 _.parser = function() {
4193 var self = this, string = Parser.string, regex = Parser.regex;
4194 return Parser.optWhitespace
4195 .then(string('{'))
4196 .then(regex(/^[-\w\s\\\xA0-\xFF]*/))
4197 .skip(string('}'))
4198 .then(function(cls) {
4199 self.htmlTemplate = '<span class="mq-class '+cls+'">&0</span>';
4200 return super_.parser.call(self);
4201 })
4202 ;
4203 };
4204});
4205
4206var SupSub = P(MathCommand, function(_, super_) {
4207 _.ctrlSeq = '_{...}^{...}';
4208 _.createLeftOf = function(cursor) {
4209 if (!cursor[L] && cursor.options.supSubsRequireOperand) return;
4210 return super_.createLeftOf.apply(this, arguments);
4211 };
4212 _.contactWeld = function(cursor) {
4213 // Look on either side for a SupSub, if one is found compare my
4214 // .sub, .sup with its .sub, .sup. If I have one that it doesn't,
4215 // then call .addBlock() on it with my block; if I have one that
4216 // it also has, then insert my block's children into its block,
4217 // unless my block has none, in which case insert the cursor into
4218 // its block (and not mine, I'm about to remove myself) in the case
4219 // I was just typed.
4220 // TODO: simplify
4221
4222 // equiv. to [L, R].forEach(function(dir) { ... });
4223 for (var dir = L; dir; dir = (dir === L ? R : false)) {
4224 if (this[dir] instanceof SupSub) {
4225 // equiv. to 'sub sup'.split(' ').forEach(function(supsub) { ... });
4226 for (var supsub = 'sub'; supsub; supsub = (supsub === 'sub' ? 'sup' : false)) {
4227 var src = this[supsub], dest = this[dir][supsub];
4228 if (!src) continue;
4229 if (!dest) this[dir].addBlock(src.disown());
4230 else if (!src.isEmpty()) { // ins src children at -dir end of dest
4231 src.jQ.children().insAtDirEnd(-dir, dest.jQ);
4232 var children = src.children().disown();
4233 var pt = Point(dest, children.ends[R], dest.ends[L]);
4234 if (dir === L) children.adopt(dest, dest.ends[R], 0);
4235 else children.adopt(dest, 0, dest.ends[L]);
4236 }
4237 else var pt = Point(dest, 0, dest.ends[L]);
4238 this.placeCursor = (function(dest, src) { // TODO: don't monkey-patch
4239 return function(cursor) { cursor.insAtDirEnd(-dir, dest || src); };
4240 }(dest, src));
4241 }
4242 this.remove();
4243 if (cursor && cursor[L] === this) {
4244 if (dir === R && pt) {
4245 pt[L] ? cursor.insRightOf(pt[L]) : cursor.insAtLeftEnd(pt.parent);
4246 }
4247 else cursor.insRightOf(this[dir]);
4248 }
4249 break;
4250 }
4251 }
4252 };
4253 Options.p.charsThatBreakOutOfSupSub = '';
4254 _.finalizeTree = function() {
4255 this.ends[L].write = function(cursor, ch) {
4256 if (cursor.options.autoSubscriptNumerals && this === this.parent.sub) {
4257 if (ch === '_') return;
4258 var cmd = this.chToCmd(ch);
4259 if (cmd instanceof Symbol) cursor.deleteSelection();
4260 else cursor.clearSelection().insRightOf(this.parent);
4261 return cmd.createLeftOf(cursor.show());
4262 }
4263 if (cursor[L] && !cursor[R] && !cursor.selection
4264 && cursor.options.charsThatBreakOutOfSupSub.indexOf(ch) > -1) {
4265 cursor.insRightOf(this.parent);
4266 }
4267 MathBlock.p.write.apply(this, arguments);
4268 };
4269 };
4270 _.moveTowards = function(dir, cursor, updown) {
4271 if (cursor.options.autoSubscriptNumerals && !this.sup) {
4272 cursor.insDirOf(dir, this);
4273 }
4274 else super_.moveTowards.apply(this, arguments);
4275 };
4276 _.deleteTowards = function(dir, cursor) {
4277 if (cursor.options.autoSubscriptNumerals && this.sub) {
4278 var cmd = this.sub.ends[-dir];
4279 if (cmd instanceof Symbol) cmd.remove();
4280 else if (cmd) cmd.deleteTowards(dir, cursor.insAtDirEnd(-dir, this.sub));
4281
4282 // TODO: factor out a .removeBlock() or something
4283 if (this.sub.isEmpty()) {
4284 this.sub.deleteOutOf(L, cursor.insAtLeftEnd(this.sub));
4285 if (this.sup) cursor.insDirOf(-dir, this);
4286 // Note `-dir` because in e.g. x_1^2| want backspacing (leftward)
4287 // to delete the 1 but to end up rightward of x^2; with non-negated
4288 // `dir` (try it), the cursor appears to have gone "through" the ^2.
4289 }
4290 }
4291 else super_.deleteTowards.apply(this, arguments);
4292 };
4293 _.latex = function() {
4294 function latex(prefix, block) {
4295 var l = block && block.latex();
4296 return block ? prefix + (l.length === 1 ? l : '{' + (l || ' ') + '}') : '';
4297 }
4298 return latex('_', this.sub) + latex('^', this.sup);
4299 };
4300 _.addBlock = function(block) {
4301 if (this.supsub === 'sub') {
4302 this.sup = this.upInto = this.sub.upOutOf = block;
4303 block.adopt(this, this.sub, 0).downOutOf = this.sub;
4304 block.jQ = $('<span class="mq-sup"/>').append(block.jQ.children())
4305 .attr(mqBlockId, block.id).prependTo(this.jQ);
4306 }
4307 else {
4308 this.sub = this.downInto = this.sup.downOutOf = block;
4309 block.adopt(this, 0, this.sup).upOutOf = this.sup;
4310 block.jQ = $('<span class="mq-sub"></span>').append(block.jQ.children())
4311 .attr(mqBlockId, block.id).appendTo(this.jQ.removeClass('mq-sup-only'));
4312 this.jQ.append('<span style="display:inline-block;width:0">​</span>');
4313 }
4314 // like 'sub sup'.split(' ').forEach(function(supsub) { ... });
4315 for (var i = 0; i < 2; i += 1) (function(cmd, supsub, oppositeSupsub, updown) {
4316 cmd[supsub].deleteOutOf = function(dir, cursor) {
4317 cursor.insDirOf((this[dir] ? -dir : dir), this.parent);
4318 if (!this.isEmpty()) {
4319 var end = this.ends[dir];
4320 this.children().disown()
4321 .withDirAdopt(dir, cursor.parent, cursor[dir], cursor[-dir])
4322 .jQ.insDirOf(-dir, cursor.jQ);
4323 cursor[-dir] = end;
4324 }
4325 cmd.supsub = oppositeSupsub;
4326 delete cmd[supsub];
4327 delete cmd[updown+'Into'];
4328 cmd[oppositeSupsub][updown+'OutOf'] = insLeftOfMeUnlessAtEnd;
4329 delete cmd[oppositeSupsub].deleteOutOf;
4330 if (supsub === 'sub') $(cmd.jQ.addClass('mq-sup-only')[0].lastChild).remove();
4331 this.remove();
4332 };
4333 }(this, 'sub sup'.split(' ')[i], 'sup sub'.split(' ')[i], 'down up'.split(' ')[i]));
4334 };
4335});
4336
4337function insLeftOfMeUnlessAtEnd(cursor) {
4338 // cursor.insLeftOf(cmd), unless cursor at the end of block, and every
4339 // ancestor cmd is at the end of every ancestor block
4340 var cmd = this.parent, ancestorCmd = cursor;
4341 do {
4342 if (ancestorCmd[R]) return cursor.insLeftOf(cmd);
4343 ancestorCmd = ancestorCmd.parent.parent;
4344 } while (ancestorCmd !== cmd);
4345 cursor.insRightOf(cmd);
4346}
4347
4348LatexCmds.subscript =
4349LatexCmds._ = P(SupSub, function(_, super_) {
4350 _.supsub = 'sub';
4351 _.htmlTemplate =
4352 '<span class="mq-supsub mq-non-leaf">'
4353 + '<span class="mq-sub">&0</span>'
4354 + '<span style="display:inline-block;width:0">​</span>'
4355 + '</span>'
4356 ;
4357 _.textTemplate = [ '_' ];
4358 _.finalizeTree = function() {
4359 this.downInto = this.sub = this.ends[L];
4360 this.sub.upOutOf = insLeftOfMeUnlessAtEnd;
4361 super_.finalizeTree.call(this);
4362 };
4363});
4364
4365LatexCmds.superscript =
4366LatexCmds.supscript =
4367LatexCmds['^'] = P(SupSub, function(_, super_) {
4368 _.supsub = 'sup';
4369 _.htmlTemplate =
4370 '<span class="mq-supsub mq-non-leaf mq-sup-only">'
4371 + '<span class="mq-sup">&0</span>'
4372 + '</span>'
4373 ;
4374 _.textTemplate = [ '^' ];
4375 _.finalizeTree = function() {
4376 this.upInto = this.sup = this.ends[R];
4377 this.sup.downOutOf = insLeftOfMeUnlessAtEnd;
4378 super_.finalizeTree.call(this);
4379 };
4380});
4381
4382var SummationNotation = P(MathCommand, function(_, super_) {
4383 _.init = function(ch, html) {
4384 var htmlTemplate =
4385 '<span class="mq-large-operator mq-non-leaf">'
4386 + '<span class="mq-to"><span>&1</span></span>'
4387 + '<big>'+html+'</big>'
4388 + '<span class="mq-from"><span>&0</span></span>'
4389 + '</span>'
4390 ;
4391 Symbol.prototype.init.call(this, ch, htmlTemplate);
4392 };
4393 _.createLeftOf = function(cursor) {
4394 super_.createLeftOf.apply(this, arguments);
4395 if (cursor.options.sumStartsWithNEquals) {
4396 Letter('n').createLeftOf(cursor);
4397 Equality().createLeftOf(cursor);
4398 }
4399 };
4400 _.latex = function() {
4401 function simplify(latex) {
4402 return latex.length === 1 ? latex : '{' + (latex || ' ') + '}';
4403 }
4404 return this.ctrlSeq + '_' + simplify(this.ends[L].latex()) +
4405 '^' + simplify(this.ends[R].latex());
4406 };
4407 _.parser = function() {
4408 var string = Parser.string;
4409 var optWhitespace = Parser.optWhitespace;
4410 var succeed = Parser.succeed;
4411 var block = latexMathParser.block;
4412
4413 var self = this;
4414 var blocks = self.blocks = [ MathBlock(), MathBlock() ];
4415 for (var i = 0; i < blocks.length; i += 1) {
4416 blocks[i].adopt(self, self.ends[R], 0);
4417 }
4418
4419 return optWhitespace.then(string('_').or(string('^'))).then(function(supOrSub) {
4420 var child = blocks[supOrSub === '_' ? 0 : 1];
4421 return block.then(function(block) {
4422 block.children().adopt(child, child.ends[R], 0);
4423 return succeed(self);
4424 });
4425 }).many().result(self);
4426 };
4427 _.finalizeTree = function() {
4428 this.downInto = this.ends[L];
4429 this.upInto = this.ends[R];
4430 this.ends[L].upOutOf = this.ends[R];
4431 this.ends[R].downOutOf = this.ends[L];
4432 };
4433});
4434
4435LatexCmds['\u2211'] =
4436LatexCmds.sum =
4437LatexCmds.summation = bind(SummationNotation,'\\sum ','∑');
4438
4439LatexCmds['\u220f'] =
4440LatexCmds.prod =
4441LatexCmds.product = bind(SummationNotation,'\\prod ','∏');
4442
4443LatexCmds.coprod =
4444LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','∐');
4445
4446LatexCmds['\u222b'] =
4447LatexCmds['int'] =
4448LatexCmds.integral = P(SummationNotation, function(_, super_) {
4449 _.init = function() {
4450 var htmlTemplate =
4451 '<span class="mq-int mq-non-leaf">'
4452 + '<big>∫</big>'
4453 + '<span class="mq-supsub mq-non-leaf">'
4454 + '<span class="mq-sup"><span class="mq-sup-inner">&1</span></span>'
4455 + '<span class="mq-sub">&0</span>'
4456 + '<span style="display:inline-block;width:0">​</span>'
4457 + '</span>'
4458 + '</span>'
4459 ;
4460 Symbol.prototype.init.call(this, '\\int ', htmlTemplate);
4461 };
4462 // FIXME: refactor rather than overriding
4463 _.createLeftOf = MathCommand.p.createLeftOf;
4464});
4465
4466var Fraction =
4467LatexCmds.frac =
4468LatexCmds.dfrac =
4469LatexCmds.cfrac =
4470LatexCmds.fraction = P(MathCommand, function(_, super_) {
4471 _.ctrlSeq = '\\frac';
4472 _.htmlTemplate =
4473 '<span class="mq-fraction mq-non-leaf">'
4474 + '<span class="mq-numerator">&0</span>'
4475 + '<span class="mq-denominator">&1</span>'
4476 + '<span style="display:inline-block;width:0">​</span>'
4477 + '</span>'
4478 ;
4479 _.textTemplate = ['(', ')/(', ')'];
4480 _.finalizeTree = function() {
4481 this.upInto = this.ends[R].upOutOf = this.ends[L];
4482 this.downInto = this.ends[L].downOutOf = this.ends[R];
4483 };
4484});
4485
4486var LiveFraction =
4487LatexCmds.over =
4488CharCmds['/'] = P(Fraction, function(_, super_) {
4489 _.createLeftOf = function(cursor) {
4490 if (!this.replacedFragment) {
4491 var leftward = cursor[L];
4492 while (leftward &&
4493 !(
4494 leftward instanceof BinaryOperator ||
4495 leftward instanceof (LatexCmds.text || noop) ||
4496 leftward instanceof SummationNotation ||
4497 leftward.ctrlSeq === '\\ ' ||
4498 /^[,;:]$/.test(leftward.ctrlSeq)
4499 ) //lookbehind for operator
4500 ) leftward = leftward[L];
4501
4502 if (leftward instanceof SummationNotation && leftward[R] instanceof SupSub) {
4503 leftward = leftward[R];
4504 if (leftward[R] instanceof SupSub && leftward[R].ctrlSeq != leftward.ctrlSeq)
4505 leftward = leftward[R];
4506 }
4507
4508 if (leftward !== cursor[L]) {
4509 this.replaces(Fragment(leftward[R] || cursor.parent.ends[L], cursor[L]));
4510 cursor[L] = leftward;
4511 }
4512 }
4513 super_.createLeftOf.call(this, cursor);
4514 };
4515});
4516
4517var SquareRoot =
4518LatexCmds.sqrt =
4519LatexCmds['\u221a'] = P(MathCommand, function(_, super_) {
4520 _.ctrlSeq = '\\sqrt';
4521 _.htmlTemplate =
4522 '<span class="mq-non-leaf">'
4523 + '<span class="mq-scaled mq-sqrt-prefix">√</span>'
4524 + '<span class="mq-non-leaf mq-sqrt-stem">&0</span>'
4525 + '</span>'
4526 ;
4527 _.textTemplate = ['sqrt(', ')'];
4528 _.parser = function() {
4529 return latexMathParser.optBlock.then(function(optBlock) {
4530 return latexMathParser.block.map(function(block) {
4531 var nthroot = NthRoot();
4532 nthroot.blocks = [ optBlock, block ];
4533 optBlock.adopt(nthroot, 0, 0);
4534 block.adopt(nthroot, optBlock, 0);
4535 return nthroot;
4536 });
4537 }).or(super_.parser.call(this));
4538 };
4539 _.reflow = function() {
4540 var block = this.ends[R].jQ;
4541 scale(block.prev(), 1, block.innerHeight()/+block.css('fontSize').slice(0,-2) - .1);
4542 };
4543});
4544
4545var Vec = LatexCmds.vec = P(MathCommand, function(_, super_) {
4546 _.ctrlSeq = '\\vec';
4547 _.htmlTemplate =
4548 '<span class="mq-non-leaf">'
4549 + '<span class="mq-vector-prefix">→</span>'
4550 + '<span class="mq-vector-stem">&0</span>'
4551 + '</span>'
4552 ;
4553 _.textTemplate = ['vec(', ')'];
4554});
4555
4556var NthRoot =
4557LatexCmds.nthroot = P(SquareRoot, function(_, super_) {
4558 _.htmlTemplate =
4559 '<sup class="mq-nthroot mq-non-leaf">&0</sup>'
4560 + '<span class="mq-scaled">'
4561 + '<span class="mq-sqrt-prefix mq-scaled">√</span>'
4562 + '<span class="mq-sqrt-stem mq-non-leaf">&1</span>'
4563 + '</span>'
4564 ;
4565 _.textTemplate = ['sqrt[', '](', ')'];
4566 _.latex = function() {
4567 return '\\sqrt['+this.ends[L].latex()+']{'+this.ends[R].latex()+'}';
4568 };
4569});
4570
4571function DelimsMixin(_, super_) {
4572 _.jQadd = function() {
4573 super_.jQadd.apply(this, arguments);
4574 this.delimjQs = this.jQ.children(':first').add(this.jQ.children(':last'));
4575 this.contentjQ = this.jQ.children(':eq(1)');
4576 };
4577 _.reflow = function() {
4578 var height = this.contentjQ.outerHeight()
4579 / parseFloat(this.contentjQ.css('fontSize'));
4580 scale(this.delimjQs, min(1 + .2*(height - 1), 1.2), 1.2*height);
4581 };
4582}
4583
4584// Round/Square/Curly/Angle Brackets (aka Parens/Brackets/Braces)
4585// first typed as one-sided bracket with matching "ghost" bracket at
4586// far end of current block, until you type an opposing one
4587var Bracket = P(P(MathCommand, DelimsMixin), function(_, super_) {
4588 _.init = function(side, open, close, ctrlSeq, end) {
4589 super_.init.call(this, '\\left'+ctrlSeq, undefined, [open, close]);
4590 this.side = side;
4591 this.sides = {};
4592 this.sides[L] = { ch: open, ctrlSeq: ctrlSeq };
4593 this.sides[R] = { ch: close, ctrlSeq: end };
4594 };
4595 _.numBlocks = function() { return 1; };
4596 _.html = function() { // wait until now so that .side may
4597 this.htmlTemplate = // be set by createLeftOf or parser
4598 '<span class="mq-non-leaf">'
4599 + '<span class="mq-scaled mq-paren'+(this.side === R ? ' mq-ghost' : '')+'">'
4600 + this.sides[L].ch
4601 + '</span>'
4602 + '<span class="mq-non-leaf">&0</span>'
4603 + '<span class="mq-scaled mq-paren'+(this.side === L ? ' mq-ghost' : '')+'">'
4604 + this.sides[R].ch
4605 + '</span>'
4606 + '</span>'
4607 ;
4608 return super_.html.call(this);
4609 };
4610 _.latex = function() {
4611 return '\\left'+this.sides[L].ctrlSeq+this.ends[L].latex()+'\\right'+this.sides[R].ctrlSeq;
4612 };
4613 _.oppBrack = function(opts, node, expectedSide) {
4614 // return node iff it's a 1-sided bracket of expected side (if any, may be
4615 // undefined), and of opposite side from me if I'm not a pipe
4616 return node instanceof Bracket && node.side && node.side !== -expectedSide
4617 && (this.sides[this.side].ch === '|' || node.side === -this.side)
4618 && (!opts.restrictMismatchedBrackets
4619 || OPP_BRACKS[this.sides[this.side].ch] === node.sides[node.side].ch
4620 || { '(': ']', '[': ')' }[this.sides[L].ch] === node.sides[R].ch) && node;
4621 };
4622 _.closeOpposing = function(brack) {
4623 brack.side = 0;
4624 brack.sides[this.side] = this.sides[this.side]; // copy over my info (may be
4625 brack.delimjQs.eq(this.side === L ? 0 : 1) // mismatched, like [a, b))
4626 .removeClass('mq-ghost').html(this.sides[this.side].ch);
4627 };
4628 _.createLeftOf = function(cursor) {
4629 if (!this.replacedFragment) { // unless wrapping seln in brackets,
4630 // check if next to or inside an opposing one-sided bracket
4631 // (must check both sides 'cos I might be a pipe)
4632 var opts = cursor.options;
4633 var brack = this.oppBrack(opts, cursor[L], L)
4634 || this.oppBrack(opts, cursor[R], R)
4635 || this.oppBrack(opts, cursor.parent.parent);
4636 }
4637 if (brack) {
4638 var side = this.side = -brack.side; // may be pipe with .side not yet set
4639 this.closeOpposing(brack);
4640 if (brack === cursor.parent.parent && cursor[side]) { // move the stuff between
4641 Fragment(cursor[side], cursor.parent.ends[side], -side) // me and ghost outside
4642 .disown().withDirAdopt(-side, brack.parent, brack, brack[side])
4643 .jQ.insDirOf(side, brack.jQ);
4644 brack.bubble('reflow');
4645 }
4646 }
4647 else {
4648 brack = this, side = brack.side;
4649 if (brack.replacedFragment) brack.side = 0; // wrapping seln, don't be one-sided
4650 else if (cursor[-side]) { // elsewise, auto-expand so ghost is at far end
4651 brack.replaces(Fragment(cursor[-side], cursor.parent.ends[-side], side));
4652 cursor[-side] = 0;
4653 }
4654 super_.createLeftOf.call(brack, cursor);
4655 }
4656 if (side === L) cursor.insAtLeftEnd(brack.ends[L]);
4657 else cursor.insRightOf(brack);
4658 };
4659 _.placeCursor = noop;
4660 _.unwrap = function() {
4661 this.ends[L].children().disown().adopt(this.parent, this, this[R])
4662 .jQ.insertAfter(this.jQ);
4663 this.remove();
4664 };
4665 _.deleteSide = function(side, outward, cursor) {
4666 var parent = this.parent, sib = this[side], farEnd = parent.ends[side];
4667
4668 if (side === this.side) { // deleting non-ghost of one-sided bracket, unwrap
4669 this.unwrap();
4670 sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
4671 return;
4672 }
4673
4674 var opts = cursor.options, wasSolid = !this.side;
4675 this.side = -side;
4676 // if deleting like, outer close-brace of [(1+2)+3} where inner open-paren
4677 if (this.oppBrack(opts, this.ends[L].ends[this.side], side)) { // is ghost,
4678 this.closeOpposing(this.ends[L].ends[this.side]); // then become [1+2)+3
4679 var origEnd = this.ends[L].ends[side];
4680 this.unwrap();
4681 if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
4682 sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
4683 }
4684 else { // if deleting like, inner close-brace of ([1+2}+3) where outer
4685 if (this.oppBrack(opts, this.parent.parent, side)) { // open-paren is
4686 this.parent.parent.closeOpposing(this); // ghost, then become [1+2+3)
4687 this.parent.parent.unwrap();
4688 } // else if deleting outward from a solid pair, unwrap
4689 else if (outward && wasSolid) {
4690 this.unwrap();
4691 sib ? cursor.insDirOf(-side, sib) : cursor.insAtDirEnd(side, parent);
4692 return;
4693 }
4694 else { // else deleting just one of a pair of brackets, become one-sided
4695 this.sides[side] = { ch: OPP_BRACKS[this.sides[this.side].ch],
4696 ctrlSeq: OPP_BRACKS[this.sides[this.side].ctrlSeq] };
4697 this.delimjQs.removeClass('mq-ghost')
4698 .eq(side === L ? 0 : 1).addClass('mq-ghost').html(this.sides[side].ch);
4699 }
4700 if (sib) { // auto-expand so ghost is at far end
4701 var origEnd = this.ends[L].ends[side];
4702 Fragment(sib, farEnd, -side).disown()
4703 .withDirAdopt(-side, this.ends[L], origEnd, 0)
4704 .jQ.insAtDirEnd(side, this.ends[L].jQ.removeClass('mq-empty'));
4705 if (origEnd.siblingCreated) origEnd.siblingCreated(cursor.options, side);
4706 cursor.insDirOf(-side, sib);
4707 } // didn't auto-expand, cursor goes just outside or just inside parens
4708 else (outward ? cursor.insDirOf(side, this)
4709 : cursor.insAtDirEnd(side, this.ends[L]));
4710 }
4711 };
4712 _.deleteTowards = function(dir, cursor) {
4713 this.deleteSide(-dir, false, cursor);
4714 };
4715 _.finalizeTree = function() {
4716 this.ends[L].deleteOutOf = function(dir, cursor) {
4717 this.parent.deleteSide(dir, true, cursor);
4718 };
4719 // FIXME HACK: after initial creation/insertion, finalizeTree would only be
4720 // called if the paren is selected and replaced, e.g. by LiveFraction
4721 this.finalizeTree = this.intentionalBlur = function() {
4722 this.delimjQs.eq(this.side === L ? 1 : 0).removeClass('mq-ghost');
4723 this.side = 0;
4724 };
4725 };
4726 _.siblingCreated = function(opts, dir) { // if something typed between ghost and far
4727 if (dir === -this.side) this.finalizeTree(); // end of its block, solidify
4728 };
4729});
4730
4731var OPP_BRACKS = {
4732 '(': ')',
4733 ')': '(',
4734 '[': ']',
4735 ']': '[',
4736 '{': '}',
4737 '}': '{',
4738 '\\{': '\\}',
4739 '\\}': '\\{',
4740 '⟨': '⟩',
4741 '⟩': '⟨',
4742 '\\langle ': '\\rangle ',
4743 '\\rangle ': '\\langle ',
4744 '|': '|'
4745};
4746
4747function bindCharBracketPair(open, ctrlSeq) {
4748 var ctrlSeq = ctrlSeq || open, close = OPP_BRACKS[open], end = OPP_BRACKS[ctrlSeq];
4749 CharCmds[open] = bind(Bracket, L, open, close, ctrlSeq, end);
4750 CharCmds[close] = bind(Bracket, R, open, close, ctrlSeq, end);
4751}
4752bindCharBracketPair('(');
4753bindCharBracketPair('[');
4754bindCharBracketPair('{', '\\{');
4755LatexCmds.langle = bind(Bracket, L, '⟨', '⟩', '\\langle ', '\\rangle ');
4756LatexCmds.rangle = bind(Bracket, R, '⟨', '⟩', '\\langle ', '\\rangle ');
4757CharCmds['|'] = bind(Bracket, L, '|', '|', '|', '|');
4758
4759LatexCmds.left = P(MathCommand, function(_) {
4760 _.parser = function() {
4761 var regex = Parser.regex;
4762 var string = Parser.string;
4763 var succeed = Parser.succeed;
4764 var optWhitespace = Parser.optWhitespace;
4765
4766 return optWhitespace.then(regex(/^(?:[([|]|\\\{)/))
4767 .then(function(ctrlSeq) { // TODO: \langle, \rangle
4768 var open = (ctrlSeq.charAt(0) === '\\' ? ctrlSeq.slice(1) : ctrlSeq);
4769 return latexMathParser.then(function (block) {
4770 return string('\\right').skip(optWhitespace)
4771 .then(regex(/^(?:[\])|]|\\\})/)).map(function(end) {
4772 var close = (end.charAt(0) === '\\' ? end.slice(1) : end);
4773 var cmd = Bracket(0, open, close, ctrlSeq, end);
4774 cmd.blocks = [ block ];
4775 block.adopt(cmd, 0, 0);
4776 return cmd;
4777 })
4778 ;
4779 });
4780 })
4781 ;
4782 };
4783});
4784
4785LatexCmds.right = P(MathCommand, function(_) {
4786 _.parser = function() {
4787 return Parser.fail('unmatched \\right');
4788 };
4789});
4790
4791var Binomial =
4792LatexCmds.binom =
4793LatexCmds.binomial = P(P(MathCommand, DelimsMixin), function(_, super_) {
4794 _.ctrlSeq = '\\binom';
4795 _.htmlTemplate =
4796 '<span class="mq-non-leaf">'
4797 + '<span class="mq-paren mq-scaled">(</span>'
4798 + '<span class="mq-non-leaf">'
4799 + '<span class="mq-array mq-non-leaf">'
4800 + '<span>&0</span>'
4801 + '<span>&1</span>'
4802 + '</span>'
4803 + '</span>'
4804 + '<span class="mq-paren mq-scaled">)</span>'
4805 + '</span>'
4806 ;
4807 _.textTemplate = ['choose(',',',')'];
4808});
4809
4810var Choose =
4811LatexCmds.choose = P(Binomial, function(_) {
4812 _.createLeftOf = LiveFraction.prototype.createLeftOf;
4813});
4814
4815LatexCmds.editable = // backcompat with before cfd3620 on #233
4816LatexCmds.MathQuillMathField = P(MathCommand, function(_, super_) {
4817 _.ctrlSeq = '\\MathQuillMathField';
4818 _.htmlTemplate =
4819 '<span class="mq-editable-field">'
4820 + '<span class="mq-root-block">&0</span>'
4821 + '</span>'
4822 ;
4823 _.parser = function() {
4824 var self = this,
4825 string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
4826 return string('[').then(regex(/^[a-z][a-z0-9]*/i)).skip(string(']'))
4827 .map(function(name) { self.name = name; }).or(succeed())
4828 .then(super_.parser.call(self));
4829 };
4830 _.finalizeTree = function() {
4831 var ctrlr = Controller(this.ends[L], this.jQ, Options());
4832 ctrlr.KIND_OF_MQ = 'MathField';
4833 ctrlr.editable = true;
4834 ctrlr.createTextarea();
4835 ctrlr.editablesTextareaEvents();
4836 ctrlr.cursor.insAtRightEnd(ctrlr.root);
4837 RootBlockMixin(ctrlr.root);
4838 };
4839 _.registerInnerField = function(innerFields, MathField) {
4840 innerFields.push(innerFields[this.name] = MathField(this.ends[L].controller));
4841 };
4842 _.latex = function(){ return this.ends[L].latex(); };
4843 _.text = function(){ return this.ends[L].text(); };
4844});
4845
4846// Embed arbitrary things
4847// Probably the closest DOM analogue would be an iframe?
4848// From MathQuill's perspective, it's a Symbol, it can be
4849// anywhere and the cursor can go around it but never in it.
4850// Create by calling public API method .dropEmbedded(),
4851// or by calling the global public API method .registerEmbed()
4852// and rendering LaTeX like \embed{registeredName} (see test).
4853var Embed = LatexCmds.embed = P(Symbol, function(_, super_) {
4854 _.setOptions = function(options) {
4855 function noop () { return ""; }
4856 this.text = options.text || noop;
4857 this.htmlTemplate = options.htmlString || "";
4858 this.latex = options.latex || noop;
4859 return this;
4860 };
4861 _.parser = function() {
4862 var self = this;
4863 string = Parser.string, regex = Parser.regex, succeed = Parser.succeed;
4864 return string('{').then(regex(/^[a-z][a-z0-9]*/i)).skip(string('}'))
4865 .then(function(name) {
4866 // the chars allowed in the optional data block are arbitrary other than
4867 // excluding curly braces and square brackets (which'd be too confusing)
4868 return string('[').then(regex(/^[-\w\s]*/)).skip(string(']'))
4869 .or(succeed()).map(function(data) {
4870 return self.setOptions(EMBEDS[name](data));
4871 })
4872 ;
4873 })
4874 ;
4875 };
4876});
4877/****************************************
4878 * Input box to type backslash commands
4879 ***************************************/
4880
4881var LatexCommandInput =
4882CharCmds['\\'] = P(MathCommand, function(_, super_) {
4883 _.ctrlSeq = '\\';
4884 _.replaces = function(replacedFragment) {
4885 this._replacedFragment = replacedFragment.disown();
4886 this.isEmpty = function() { return false; };
4887 };
4888 _.htmlTemplate = '<span class="mq-latex-command-input mq-non-leaf">\\<span>&0</span></span>';
4889 _.textTemplate = ['\\'];
4890 _.createBlocks = function() {
4891 super_.createBlocks.call(this);
4892 this.ends[L].focus = function() {
4893 this.parent.jQ.addClass('mq-hasCursor');
4894 if (this.isEmpty())
4895 this.parent.jQ.removeClass('mq-empty');
4896
4897 return this;
4898 };
4899 this.ends[L].blur = function() {
4900 this.parent.jQ.removeClass('mq-hasCursor');
4901 if (this.isEmpty())
4902 this.parent.jQ.addClass('mq-empty');
4903
4904 return this;
4905 };
4906 this.ends[L].write = function(cursor, ch) {
4907 cursor.show().deleteSelection();
4908
4909 if (ch.match(/[a-z]/i)) VanillaSymbol(ch).createLeftOf(cursor);
4910 else {
4911 this.parent.renderCommand(cursor);
4912 if (ch !== '\\' || !this.isEmpty()) this.parent.parent.write(cursor, ch);
4913 }
4914 };
4915 this.ends[L].keystroke = function(key, e, ctrlr) {
4916 if (key === 'Tab' || key === 'Enter' || key === 'Spacebar') {
4917 this.parent.renderCommand(ctrlr.cursor);
4918 e.preventDefault();
4919 return;
4920 }
4921 return super_.keystroke.apply(this, arguments);
4922 };
4923 };
4924 _.createLeftOf = function(cursor) {
4925 super_.createLeftOf.call(this, cursor);
4926
4927 if (this._replacedFragment) {
4928 var el = this.jQ[0];
4929 this.jQ =
4930 this._replacedFragment.jQ.addClass('mq-blur').bind(
4931 'mousedown mousemove', //FIXME: is monkey-patching the mousedown and mousemove handlers the right way to do this?
4932 function(e) {
4933 $(e.target = el).trigger(e);
4934 return false;
4935 }
4936 ).insertBefore(this.jQ).add(this.jQ);
4937 }
4938 };
4939 _.latex = function() {
4940 return '\\' + this.ends[L].latex() + ' ';
4941 };
4942 _.renderCommand = function(cursor) {
4943 this.jQ = this.jQ.last();
4944 this.remove();
4945 if (this[R]) {
4946 cursor.insLeftOf(this[R]);
4947 } else {
4948 cursor.insAtRightEnd(this.parent);
4949 }
4950
4951 var latex = this.ends[L].latex();
4952 if (!latex) latex = ' ';
4953 var cmd = LatexCmds[latex];
4954 if (cmd) {
4955 cmd = cmd(latex);
4956 if (this._replacedFragment) cmd.replaces(this._replacedFragment);
4957 cmd.createLeftOf(cursor);
4958 }
4959 else {
4960 cmd = TextBlock();
4961 cmd.replaces(latex);
4962 cmd.createLeftOf(cursor);
4963 cursor.insRightOf(cmd);
4964 if (this._replacedFragment)
4965 this._replacedFragment.remove();
4966 }
4967 };
4968});
4969
4970var MQ1 = getInterface(1);
4971for (var key in MQ1) (function(key, val) {
4972 if (typeof val === 'function') {
4973 MathQuill[key] = function() {
4974 insistOnInterVer();
4975 return val.apply(this, arguments);
4976 };
4977 MathQuill[key].prototype = val.prototype;
4978 }
4979 else MathQuill[key] = val;
4980}(key, MQ1[key]));
4981
4982}());