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