mathquill_rails_dev 0.9.3.0

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