/**
* Copyleft 2010-2011 Jay and Han (laughinghan@gmail.com)
* under the GNU Lesser General Public License
* http://www.gnu.org/licenses/lgpl.html
* Project Website: http://mathquill.com
*/
(function() {
var jQuery = window.jQuery,
undefined,
mqCmdId = 'mathquill-command-id',
mqBlockId = 'mathquill-block-id',
min = Math.min,
max = Math.max;
function noop() {}
/**
* A utility higher-order function that makes defining variadic
* functions more convenient by letting you essentially define functions
* with the last argument as a splat, i.e. the last argument "gathers up"
* remaining arguments to the function:
* var doStuff = variadic(function(first, rest) { return rest; });
* doStuff(1, 2, 3); // => [2, 3]
*/
var __slice = [].slice;
function variadic(fn) {
var numFixedArgs = fn.length - 1;
return function() {
var args = __slice.call(arguments, 0, numFixedArgs);
var varArg = __slice.call(arguments, numFixedArgs);
return fn.apply(this, args.concat([ varArg ]));
};
}
/**
* A utility higher-order function that makes combining object-oriented
* programming and functional programming techniques more convenient:
* given a method name and any number of arguments to be bound, returns
* a function that calls it's first argument's method of that name (if
* it exists) with the bound arguments and any additional arguments that
* are passed:
* var sendMethod = send('method', 1, 2);
* var obj = { method: function() { return Array.apply(this, arguments); } };
* sendMethod(obj, 3, 4); // => [1, 2, 3, 4]
* // or more specifically,
* var obj2 = { method: function(one, two, three) { return one*two + three; } };
* sendMethod(obj2, 3); // => 5
* sendMethod(obj2, 4); // => 6
*/
var send = variadic(function(method, args) {
return variadic(function(obj, moreArgs) {
if (method in obj) return obj[method].apply(obj, args.concat(moreArgs));
});
});
/**
* A utility higher-order function that creates "implicit iterators"
* from "generators": given a function that takes in a sole argument,
* a "yield" function, that calls "yield" repeatedly with an object as
* a sole argument (presumably objects being iterated over), returns
* a function that calls it's first argument on each of those objects
* (if the first argument is a function, it is called repeatedly with
* each object as the first argument, otherwise it is stringified and
* the method of that name is called on each object (if such a method
* exists)), passing along all additional arguments:
* var a = [
* { method: function(list) { list.push(1); } },
* { method: function(list) { list.push(2); } },
* { method: function(list) { list.push(3); } }
* ];
* a.each = iterator(function(yield) {
* for (var i in this) yield(this[i]);
* });
* var list = [];
* a.each('method', list);
* list; // => [1, 2, 3]
* // Note that the for-in loop will yield 'each', but 'each' maps to
* // the function object created by iterator() which does not have a
* // .method() method, so that just fails silently.
*/
function iterator(generator) {
return variadic(function(fn, args) {
if (typeof fn !== 'function') fn = send(fn);
var yield = function(obj) { return fn.apply(obj, [ obj ].concat(args)); };
return generator.call(this, yield);
});
}
/**
* sugar to make defining lots of commands easier.
* TODO: rethink this.
*/
function bind(cons /*, args... */) {
var args = __slice.call(arguments, 1);
return function() {
return cons.apply(this, args);
};
}
/**
* a development-only debug method. This definition and all
* calls to `pray` will be stripped from the minified
* build of mathquill.
*
* This function must be called by name to be removed
* at compile time. Do not define another function
* with the same name, and only call this function by
* name.
*/
function pray(message, cond) {
if (!cond) throw new Error('prayer failed: '+message);
}
var P = (function(prototype, ownProperty, undefined) {
// helper functions that also help minification
function isObject(o) { return typeof o === 'object'; }
function isFunction(f) { return typeof f === 'function'; }
// used to extend the prototypes of superclasses (which might not
// have `.Bare`s)
function SuperclassBare() {}
return function P(_superclass /* = Object */, definition) {
// handle the case where no superclass is given
if (definition === undefined) {
definition = _superclass;
_superclass = Object;
}
// C is the class to be returned.
//
// It delegates to instantiating an instance of `Bare`, so that it
// will always return a new instance regardless of the calling
// context.
//
// TODO: the Chrome inspector shows all created objects as `C`
// rather than `Object`. Setting the .name property seems to
// have no effect. Is there a way to override this behavior?
function C() {
var self = new Bare;
if (isFunction(self.init)) self.init.apply(self, arguments);
return self;
}
// C.Bare is a class with a noop constructor. Its prototype is the
// same as C, so that instances of C.Bare are also instances of C.
// New objects can be allocated without initialization by calling
// `new MyClass.Bare`.
function Bare() {}
C.Bare = Bare;
// Set up the prototype of the new class.
var _super = SuperclassBare[prototype] = _superclass[prototype];
var proto = Bare[prototype] = C[prototype] = C.p = new SuperclassBare;
// other variables, as a minifier optimization
var extensions;
// set the constructor property on the prototype, for convenience
proto.constructor = C;
C.mixin = function(def) {
Bare[prototype] = C[prototype] = P(C, def)[prototype];
return C;
}
return (C.open = function(def) {
extensions = {};
if (isFunction(def)) {
// call the defining function with all the arguments you need
// extensions captures the return value.
extensions = def.call(C, proto, _super, C, _superclass);
}
else if (isObject(def)) {
// if you passed an object instead, we'll take it
extensions = def;
}
// ...and extend it
if (isObject(extensions)) {
for (var ext in extensions) {
if (ownProperty.call(extensions, ext)) {
proto[ext] = extensions[ext];
}
}
}
// if there's no init, we assume we're inheriting a non-pjs class, so
// we default to applying the superclass's constructor.
if (!isFunction(proto.init)) {
proto.init = _superclass;
}
return C;
})(definition);
}
// as a minifier optimization, we've closured in a few helper functions
// and the string 'prototype' (C[p] is much shorter than C.prototype)
})('prototype', ({}).hasOwnProperty);
/*************************************************
* Textarea Manager
*
* An abstraction layer wrapping the textarea in
* an object with methods to manipulate and listen
* to events on, that hides all the nasty cross-
* browser incompatibilities behind a uniform API.
*
* Design goal: This is a *HARD* internal
* abstraction barrier. Cross-browser
* inconsistencies are not allowed to leak through
* and be dealt with by event handlers. All future
* cross-browser issues that arise must be dealt
* with here, and if necessary, the API updated.
*
* Organization:
* - key values map and stringify()
* - manageTextarea()
* + defer() and flush()
* + event handler logic
* + attach event handlers and export methods
************************************************/
var manageTextarea = (function() {
// The following [key values][1] map was compiled from the
// [DOM3 Events appendix section on key codes][2] and
// [a widely cited report on cross-browser tests of key codes][3],
// except for 10: 'Enter', which I've empirically observed in Safari on iOS
// and doesn't appear to conflict with any other known key codes.
//
// [1]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#keys-keyvalues
// [2]: http://www.w3.org/TR/2012/WD-DOM-Level-3-Events-20120614/#fixed-virtual-key-codes
// [3]: http://unixpapa.com/js/key.html
var KEY_VALUES = {
8: 'Backspace',
9: 'Tab',
10: 'Enter', // for Safari on iOS
13: 'Enter',
16: 'Shift',
17: 'Control',
18: 'Alt',
20: 'CapsLock',
27: 'Esc',
32: 'Spacebar',
33: 'PageUp',
34: 'PageDown',
35: 'End',
36: 'Home',
37: 'Left',
38: 'Up',
39: 'Right',
40: 'Down',
45: 'Insert',
46: 'Del',
144: 'NumLock'
};
// To the extent possible, create a normalized string representation
// of the key combo (i.e., key code and modifier keys).
function stringify(evt) {
var which = evt.which || evt.keyCode;
var keyVal = KEY_VALUES[which];
var key;
var modifiers = [];
if (evt.ctrlKey) modifiers.push('Ctrl');
if (evt.originalEvent && evt.originalEvent.metaKey) modifiers.push('Meta');
if (evt.altKey) modifiers.push('Alt');
if (evt.shiftKey) modifiers.push('Shift');
key = keyVal || String.fromCharCode(which);
if (!modifiers.length && !keyVal) return key;
modifiers.push(key);
return modifiers.join('-');
}
// create a textarea manager that calls callbacks at useful times
// and exports useful public methods
return function manageTextarea(el, opts) {
var keydown = null;
var keypress = null;
if (!opts) opts = {};
var textCallback = opts.text || noop;
var keyCallback = opts.key || noop;
var pasteCallback = opts.paste || noop;
var onCut = opts.cut || noop;
var textarea = jQuery(el);
var target = jQuery(opts.container || textarea);
// checkTextareaFor() is called after keypress or paste events to
// say "Hey, I think something was just typed" or "pasted" (resp.),
// so that at all subsequent opportune times (next event or timeout),
// will check for expected typed or pasted text.
// Need to check repeatedly because #135: in Safari 5.1 (at least),
// after selecting something and then typing, the textarea is
// incorrectly reported as selected during the input event (but not
// subsequently).
var checkTextarea = noop, timeoutId;
function checkTextareaFor(checker) {
checkTextarea = checker;
clearTimeout(timeoutId);
timeoutId = setTimeout(checker);
}
target.bind('keydown keypress input keyup focusout paste', function() { checkTextarea(); });
// -*- public methods -*- //
function select(text) {
checkTextarea();
textarea.val(text);
if (text) textarea[0].select();
}
// -*- helper subroutines -*- //
// Determine whether there's a selection in the textarea.
// This will always return false in IE < 9, which don't support
// HTMLTextareaElement::selection{Start,End}.
function hasSelection() {
var dom = textarea[0];
if (!('selectionStart' in dom)) return false;
return dom.selectionStart !== dom.selectionEnd;
}
function popText(callback) {
var text = textarea.val();
textarea.val('');
if (text) callback(text);
}
function handleKey() {
keyCallback(stringify(keydown), keydown);
}
// -*- event handlers -*- //
function onKeydown(e) {
keydown = e;
keypress = null;
handleKey();
}
function onKeypress(e) {
// call the key handler for repeated keypresses.
// This excludes keypresses that happen directly
// after keydown. In that case, there will be
// no previous keypress, so we skip it here
if (keydown && keypress) handleKey();
keypress = e;
checkTextareaFor(typedText);
}
function typedText() {
// If there is a selection, the contents of the textarea couldn't
// possibly have just been typed in.
// This happens in browsers like Firefox and Opera that fire
// keypress for keystrokes that are not text entry and leave the
// selection in the textarea alone, such as Ctrl-C.
// Note: we assume that browsers that don't support hasSelection()
// also never fire keypress on keystrokes that are not text entry.
// This seems reasonably safe because:
// - all modern browsers including IE 9+ support hasSelection(),
// making it extremely unlikely any browser besides IE < 9 won't
// - as far as we know IE < 9 never fires keypress on keystrokes
// that aren't text entry, which is only as reliable as our
// tests are comprehensive, but the IE < 9 way to do
// hasSelection() is poorly documented and is also only as
// reliable as our tests are comprehensive
// If anything like #40 or #71 is reported in IE < 9, see
// b1318e5349160b665003e36d4eedd64101ceacd8
if (hasSelection()) return;
popText(textCallback);
}
function onBlur() { keydown = keypress = null; }
function onPaste(e) {
// browsers are dumb.
//
// In Linux, middle-click pasting causes onPaste to be called,
// when the textarea is not necessarily focused. We focus it
// here to ensure that the pasted text actually ends up in the
// textarea.
//
// It's pretty nifty that by changing focus in this handler,
// we can change the target of the default action. (This works
// on keydown too, FWIW).
//
// And by nifty, we mean dumb (but useful sometimes).
textarea.focus();
checkTextareaFor(pastedText);
}
function pastedText() {
popText(pasteCallback);
}
// -*- attach event handlers -*- //
target.bind({
keydown: onKeydown,
keypress: onKeypress,
focusout: onBlur,
cut: onCut,
paste: onPaste
});
// -*- export public methods -*- //
return {
select: select
};
};
}());
var Parser = P(function(_, _super, Parser) {
// The Parser object is a wrapper for a parser function.
// Externally, you use one to parse a string by calling
// var result = SomeParser.parse('Me Me Me! Parse Me!');
// You should never call the constructor, rather you should
// construct your Parser from the base parsers and the
// parser combinator methods.
function parseError(stream, message) {
if (stream) {
stream = "'"+stream+"'";
}
else {
stream = 'EOF';
}
throw 'Parse Error: '+message+' at '+stream;
}
_.init = function(body) { this._ = body; };
_.parse = function(stream) {
return this.skip(eof)._(stream, success, parseError);
function success(stream, result) { return result; }
};
// -*- primitive combinators -*- //
_.or = function(alternative) {
pray('or is passed a parser', alternative instanceof Parser);
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
return self._(stream, onSuccess, failure);
function failure(newStream) {
return alternative._(stream, onSuccess, onFailure);
}
});
};
_.then = function(next) {
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
return self._(stream, success, onFailure);
function success(newStream, result) {
var nextParser = (next instanceof Parser ? next : next(result));
pray('a parser is returned', nextParser instanceof Parser);
return nextParser._(newStream, onSuccess, onFailure);
}
});
};
// -*- optimized iterative combinators -*- //
_.many = function() {
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
var xs = [];
while (self._(stream, success, failure));
return onSuccess(stream, xs);
function success(newStream, x) {
stream = newStream;
xs.push(x);
return true;
}
function failure() {
return false;
}
});
};
_.times = function(min, max) {
if (arguments.length < 2) max = min;
var self = this;
return Parser(function(stream, onSuccess, onFailure) {
var xs = [];
var result = true;
var failure;
for (var i = 0; i < min; i += 1) {
result = self._(stream, success, firstFailure);
if (!result) return onFailure(stream, failure);
}
for (; i < max && result; i += 1) {
result = self._(stream, success, secondFailure);
}
return onSuccess(stream, xs);
function success(newStream, x) {
xs.push(x);
stream = newStream;
return true;
}
function firstFailure(newStream, msg) {
failure = msg;
stream = newStream;
return false;
}
function secondFailure(newStream, msg) {
return false;
}
});
};
// -*- higher-level combinators -*- //
_.result = function(res) { return this.then(succeed(res)); };
_.atMost = function(n) { return this.times(0, n); };
_.atLeast = function(n) {
var self = this;
return self.times(n).then(function(start) {
return self.many().map(function(end) {
return start.concat(end);
});
});
};
_.map = function(fn) {
return this.then(function(result) { return succeed(fn(result)); });
};
_.skip = function(two) {
return this.then(function(result) { return two.result(result); });
};
// -*- primitive parsers -*- //
var string = this.string = function(str) {
var len = str.length;
var expected = "expected '"+str+"'";
return Parser(function(stream, onSuccess, onFailure) {
var head = stream.slice(0, len);
if (head === str) {
return onSuccess(stream.slice(len), head);
}
else {
return onFailure(stream, expected);
}
});
};
var regex = this.regex = function(re) {
pray('regexp parser is anchored', re.toString().charAt(1) === '^');
var expected = 'expected '+re;
return Parser(function(stream, onSuccess, onFailure) {
var match = re.exec(stream);
if (match) {
var result = match[0];
return onSuccess(stream.slice(result.length), result);
}
else {
return onFailure(stream, expected);
}
});
};
var succeed = Parser.succeed = function(result) {
return Parser(function(stream, onSuccess) {
return onSuccess(stream, result);
});
};
var fail = Parser.fail = function(msg) {
return Parser(function(stream, _, onFailure) {
return onFailure(stream, msg);
});
};
var letter = Parser.letter = regex(/^[a-z]/i);
var letters = Parser.letters = regex(/^[a-z]*/i);
var digit = Parser.digit = regex(/^[0-9]/);
var digits = Parser.digits = regex(/^[0-9]*/);
var whitespace = Parser.whitespace = regex(/^\s+/);
var optWhitespace = Parser.optWhitespace = regex(/^\s*/);
var any = Parser.any = Parser(function(stream, onSuccess, onFailure) {
if (!stream) return onFailure(stream, 'expected any character');
return onSuccess(stream.slice(1), stream.charAt(0));
});
var all = Parser.all = Parser(function(stream, onSuccess, onFailure) {
return onSuccess('', stream);
});
var eof = Parser.eof = Parser(function(stream, onSuccess, onFailure) {
if (stream) return onFailure(stream, 'expected EOF');
return onSuccess(stream, stream);
});
});
/*************************************************
* Base classes of the MathQuill virtual DOM tree
*
* Only doing tree node manipulation via these
* adopt/ disown methods guarantees well-formedness
* of the tree.
************************************************/
// L = 'left'
// R = 'right'
//
// the contract is that they can be used as object properties
// and (-L) === R, and (-R) === L.
var L = -1;
var R = 1;
function prayDirection(dir) {
pray('a direction was passed', dir === L || dir === R);
}
/**
* Tiny extension of jQuery adding directionalized DOM manipulation methods.
*
* Funny how Pjs v3 almost just works with `jQuery.fn.init`.
*
* jQuery features that don't work on $:
* - jQuery.*, like jQuery.ajax, obviously (Pjs doesn't and shouldn't
* copy constructor properties)
*
* - jQuery(function), the shortcut for `jQuery(document).ready(function)`,
* because `jQuery.fn.init` is idiosyncratic and Pjs doing, essentially,
* `jQuery.fn.init.apply(this, arguments)` isn't quite right, you need:
*
* _.init = function(s, c) { jQuery.fn.init.call(this, s, c, $(document)); };
*
* if you actually give a shit (really, don't bother),
* see https://github.com/jquery/jquery/blob/1.7.2/src/core.js#L889
*
* - jQuery(selector), because jQuery translates that to
* `jQuery(document).find(selector)`, but Pjs doesn't (should it?) let
* you override the result of a constructor call
* + note that because of the jQuery(document) shortcut-ness, there's also
* the 3rd-argument-needs-to-be-`$(document)` thing above, but the fix
* for that (as can be seen above) is really easy. This problem requires
* a way more intrusive fix
*
* And that's it! Everything else just magically works because jQuery internally
* uses `this.constructor()` everywhere (hence calling `$`), but never ever does
* `this.constructor.find` or anything like that, always doing `jQuery.find`.
*/
var $ = P(jQuery, function(_) {
_.insDirOf = function(dir, el) {
return dir === L ?
this.insertBefore(el.first()) : this.insertAfter(el.last());
};
_.insAtDirEnd = function(dir, el) {
return dir === L ? this.prependTo(el) : this.appendTo(el);
};
});
var Point = P(function(_) {
_.parent = 0;
_[L] = 0;
_[R] = 0;
_.init = function(parent, leftward, rightward) {
this.parent = parent;
this[L] = leftward;
this[R] = rightward;
};
this.copy = function(pt) {
return Point(pt.parent, pt[L], pt[R]);
};
});
/**
* MathQuill virtual-DOM tree-node abstract base class
*/
var Node = P(function(_) {
_[L] = 0;
_[R] = 0
_.parent = 0;
var id = 0;
function uniqueNodeId() { return id += 1; }
this.byId = {};
_.init = function() {
this.id = uniqueNodeId();
Node.byId[this.id] = this;
this.ends = {};
this.ends[L] = 0;
this.ends[R] = 0;
};
_.dispose = function() { delete Node.byId[this.id]; };
_.toString = function() { return '{{ MathQuill Node #'+this.id+' }}'; };
_.jQ = $();
_.jQadd = function(jQ) { return this.jQ = this.jQ.add(jQ); };
_.jQize = function() {
// jQuery-ifies this.html() and links up the .jQ of all corresponding Nodes
var jQ = $(this.html());
jQ.find('*').andSelf().each(function() {
var jQ = $(this),
cmdId = jQ.attr('mathquill-command-id'),
blockId = jQ.attr('mathquill-block-id');
if (cmdId) Node.byId[cmdId].jQadd(jQ);
if (blockId) Node.byId[blockId].jQadd(jQ);
});
return jQ;
};
_.createDir = function(dir, cursor) {
prayDirection(dir);
var node = this;
node.jQize();
node.jQ.insDirOf(dir, cursor.jQ);
cursor[dir] = node.adopt(cursor.parent, cursor[L], cursor[R]);
return node;
};
_.createBefore = function(el) { return this.createDir(L, el); };
_.respace = noop;
_.bubble = iterator(function(yield) {
for (var ancestor = this; ancestor; ancestor = ancestor.parent) {
var result = yield(ancestor);
if (result === false) break;
}
return this;
});
_.postOrder = iterator(function(yield) {
(function recurse(descendant) {
descendant.eachChild(recurse);
yield(descendant);
})(this);
return this;
});
_.children = function() {
return Fragment(this.ends[L], this.ends[R]);
};
_.eachChild = function() {
var children = this.children();
children.each.apply(children, arguments);
return this;
};
_.foldChildren = function(fold, fn) {
return this.children().fold(fold, fn);
};
_.adopt = function(parent, leftward, rightward) {
Fragment(this, this).adopt(parent, leftward, rightward);
return this;
};
_.disown = function() {
Fragment(this, this).disown();
return this;
};
_.remove = function() {
this.jQ.remove();
this.postOrder('dispose');
return this.disown();
};
});
/**
* An entity outside the virtual tree with one-way pointers (so it's only a
* "view" of part of the tree, not an actual node/entity in the tree) that
* delimits a doubly-linked list of sibling nodes.
* It's like a fanfic love-child between HTML DOM DocumentFragment and the Range
* classes: like DocumentFragment, its contents must be sibling nodes
* (unlike Range, whose contents are arbitrary contiguous pieces of subtrees),
* but like Range, it has only one-way pointers to its contents, its contents
* have no reference to it and in fact may still be in the visible tree (unlike
* DocumentFragment, whose contents must be detached from the visible tree
* and have their 'parent' pointers set to the DocumentFragment).
*/
var Fragment = P(function(_) {
_.init = function(leftEnd, rightEnd) {
pray('no half-empty fragments', !leftEnd === !rightEnd);
this.ends = {};
if (!leftEnd) return;
pray('left end node is passed to Fragment', leftEnd instanceof Node);
pray('right end node is passed to Fragment', rightEnd instanceof Node);
pray('leftEnd and rightEnd have the same parent',
leftEnd.parent === rightEnd.parent);
this.ends[L] = leftEnd;
this.ends[R] = rightEnd;
this.jQ = this.fold(this.jQ, function(jQ, el) { return jQ.add(el.jQ); });
};
_.jQ = $();
function prayWellFormed(parent, leftward, rightward) {
pray('a parent is always present', parent);
pray('leftward is properly set up', (function() {
// either it's empty and `rightward` is the left end child (possibly empty)
if (!leftward) return parent.ends[L] === rightward;
// or it's there and its [R] and parent are properly set up
return leftward[R] === rightward && leftward.parent === parent;
})());
pray('rightward is properly set up', (function() {
// either it's empty and `leftward` is the right end child (possibly empty)
if (!rightward) return parent.ends[R] === leftward;
// or it's there and its [L] and parent are properly set up
return rightward[L] === leftward && rightward.parent === parent;
})());
}
_.adopt = function(parent, leftward, rightward) {
prayWellFormed(parent, leftward, rightward);
var self = this;
self.disowned = false;
var leftEnd = self.ends[L];
if (!leftEnd) return this;
var rightEnd = self.ends[R];
if (leftward) {
// NB: this is handled in the ::each() block
// leftward[R] = leftEnd
} else {
parent.ends[L] = leftEnd;
}
if (rightward) {
rightward[L] = rightEnd;
} else {
parent.ends[R] = rightEnd;
}
self.ends[R][R] = rightward;
self.each(function(el) {
el[L] = leftward;
el.parent = parent;
if (leftward) leftward[R] = el;
leftward = el;
});
return self;
};
_.disown = function() {
var self = this;
var leftEnd = self.ends[L];
// guard for empty and already-disowned fragments
if (!leftEnd || self.disowned) return self;
self.disowned = true;
var rightEnd = self.ends[R]
var parent = leftEnd.parent;
prayWellFormed(parent, leftEnd[L], leftEnd);
prayWellFormed(parent, rightEnd, rightEnd[R]);
if (leftEnd[L]) {
leftEnd[L][R] = rightEnd[R];
} else {
parent.ends[L] = rightEnd[R];
}
if (rightEnd[R]) {
rightEnd[R][L] = leftEnd[L];
} else {
parent.ends[R] = leftEnd[L];
}
return self;
};
_.remove = function() {
this.jQ.remove();
this.each('postOrder', 'dispose');
return this.disown();
};
_.each = iterator(function(yield) {
var self = this;
var el = self.ends[L];
if (!el) return self;
for (; el !== self.ends[R][R]; el = el[R]) {
var result = yield(el);
if (result === false) break;
}
return self;
});
_.fold = function(fold, fn) {
this.each(function(el) {
fold = fn.call(this, fold, el);
});
return fold;
};
// create and return the Fragment between Point A and Point B, or if they
// don't share a parent, between the ancestor of A and the ancestor of B
// who share a common parent (which would be the lowest common ancestor (LCA)
// of A and B)
// There must exist an LCA, i.e., A and B must be in the same tree, and A
// and B must not be the same Point.
this.between = function(A, B) {
pray('A and B are not the same Point',
A.parent !== B.parent || A[L] !== B[L] || A[R] !== B[R]
);
var ancA = A; // an ancestor of A
var ancB = B; // an ancestor of B
var ancMapA = {}; // a map from the id of each ancestor of A visited
// so far, to the child of that ancestor who is also an ancestor of B, e.g.
// the LCA's id maps to the ancestor of the cursor whose parent is the LCA
var ancMapB = {}; // a map of the castle and school grounds magically
// displaying the current location of everyone within the covered area,
// activated by pointing one's wand at it and saying "I solemnly swear
// that I am up to no good".
// What do you mean, you expected it to be the same as ancMapA, but
// ancestors of B instead? That's a complete non sequitur.
do {
ancMapA[ancA.parent.id] = ancA;
ancMapB[ancB.parent.id] = ancB;
if (ancB.parent.id in ancMapA) {
ancA = ancMapA[ancB.parent.id];
break;
}
if (ancA.parent.id in ancMapB) {
ancB = ancMapB[ancA.parent.id];
break;
}
if (ancA.parent) ancA = ancA.parent;
if (ancB.parent) ancB = ancB.parent;
} while (ancA.parent || ancB.parent);
// the only way for this condition to fail is if A and B are in separate
// trees, which should be impossible, but infinite loops must never happen,
// even under error conditions.
pray('A and B are in the same tree', ancA.parent || ancB.parent);
// Now we have two either Nodes or Points, guaranteed to have a common
// parent and guaranteed that if both are Points, they are not the same,
// and we have to figure out which is on the left and which on the right
// of the selection.
var left, right;
// This is an extremely subtle algorithm.
// As a special case, ancA could be a Point and ancB a Node immediately
// to ancA's left.
// In all other cases,
// - both Nodes
// - ancA a Point and ancB a Node
// - ancA a Node and ancB a Point
// ancB[R] === rightward[R] for some rightward that is ancA or to its
// right if and only if anticursorA is to the right of cursorA.
if (ancA[L] !== ancB) {
for (var rightward = ancA; rightward; rightward = rightward[R]) {
if (rightward[R] === ancB[R]) {
left = ancA;
right = ancB;
break;
}
}
}
if (!left) {
left = ancB;
right = ancA;
}
// only want to select Nodes up to Points, can't select Points themselves
if (left instanceof Point) left = left[R];
if (right instanceof Point) right = right[L];
return Fragment(left, right);
};
});
/*************************************************
* Abstract classes of math blocks and commands.
************************************************/
/**
* Math tree node base class.
* Some math-tree-specific extensions to Node.
* Both MathBlock's and MathCommand's descend from it.
*/
var MathElement = P(Node, function(_, _super) {
_.finalizeInsert = function() {
var self = this;
self.postOrder('finalizeTree');
// note: this order is important.
// empty elements need the empty box provided by blur to
// be present in order for their dimensions to be measured
// correctly in redraw.
self.postOrder('blur');
// adjust context-sensitive spacing
self.postOrder('respace');
if (self[R].respace) self[R].respace();
if (self[L].respace) self[L].respace();
self.postOrder('redraw');
self.bubble('redraw');
};
});
/**
* Commands and operators, like subscripts, exponents, or fractions.
* Descendant commands are organized into blocks.
*/
var MathCommand = P(MathElement, function(_, _super) {
_.init = function(ctrlSeq, htmlTemplate, textTemplate) {
var cmd = this;
_super.init.call(cmd);
if (!cmd.ctrlSeq) cmd.ctrlSeq = ctrlSeq;
if (htmlTemplate) cmd.htmlTemplate = htmlTemplate;
if (textTemplate) cmd.textTemplate = textTemplate;
};
// obvious methods
_.replaces = function(replacedFragment) {
replacedFragment.disown();
this.replacedFragment = replacedFragment;
};
_.isEmpty = function() {
return this.foldChildren(true, function(isEmpty, child) {
return isEmpty && child.isEmpty();
});
};
_.parser = function() {
var block = latexMathParser.block;
var self = this;
return block.times(self.numBlocks()).map(function(blocks) {
self.blocks = blocks;
for (var i = 0; i < blocks.length; i += 1) {
blocks[i].adopt(self, self.ends[R], 0);
}
return self;
});
};
// createBefore(cursor) and the methods it calls
_.createBefore = function(cursor) {
var cmd = this;
var replacedFragment = cmd.replacedFragment;
cmd.createBlocks();
_super.createBefore.call(cmd, cursor);
if (replacedFragment) {
replacedFragment.adopt(cmd.ends[L], 0, 0);
replacedFragment.jQ.appendTo(cmd.ends[L].jQ);
}
cmd.finalizeInsert(cursor);
cmd.placeCursor(cursor);
};
_.createBlocks = function() {
var cmd = this,
numBlocks = cmd.numBlocks(),
blocks = cmd.blocks = Array(numBlocks);
for (var i = 0; i < numBlocks; i += 1) {
var newBlock = blocks[i] = MathBlock();
newBlock.adopt(cmd, cmd.ends[R], 0);
}
};
_.placeCursor = function(cursor) {
//insert the cursor at the right end of the first empty child, searching
//left-to-right, or if none empty, the right end child
cursor.insAtRightEnd(this.foldChildren(this.ends[L], function(leftward, child) {
return leftward.isEmpty() ? leftward : child;
}));
};
// editability methods: called by the cursor for editing, cursor movements,
// and selection of the MathQuill tree, these all take in a direction and
// the cursor
_.moveTowards = function(dir, cursor) { cursor.insAtDirEnd(-dir, this.ends[-dir]); };
_.deleteTowards = function(dir, cursor) { cursor.selectDir(dir); };
_.selectTowards = function(dir, cursor) {
if (!cursor.anticursor) cursor.startSelection();
cursor[-dir] = this;
cursor[dir] = this[dir];
};
_.selectChildren = function(cursor) {
cursor.selection = Selection(this);
};
_.unselectInto = function(dir, cursor) {
cursor.insAtDirEnd(-dir, this.selectedOutOf);
};
_.seek = function(pageX, cursor) {
function getBounds(node) {
var bounds = {}
bounds[L] = node.jQ.offset().left;
bounds[R] = bounds[L] + node.jQ.outerWidth();
return bounds;
}
var cmd = this;
var cmdBounds = getBounds(cmd);
if (pageX < cmdBounds[L]) return cursor.insLeftOf(cmd);
if (pageX > cmdBounds[R]) return cursor.insRightOf(cmd);
var leftLeftBound = cmdBounds[L];
cmd.eachChild(function(block) {
var blockBounds = getBounds(block);
if (pageX < blockBounds[L]) {
// closer to this block's left bound, or the bound left of that?
if (pageX - leftLeftBound < blockBounds[L] - pageX) {
if (block[L]) cursor.insAtRightEnd(block[L]);
else cursor.insLeftOf(cmd);
}
else cursor.insAtLeftEnd(block);
return false;
}
else if (pageX > blockBounds[R]) {
if (block[R]) leftLeftBound = blockBounds[R]; // continue to next block
else { // last (rightmost) block
// closer to this block's right bound, or the cmd's right bound?
if (cmdBounds[R] - pageX < pageX - blockBounds[R]) {
cursor.insRightOf(cmd);
}
else cursor.insAtRightEnd(block);
}
}
else {
block.seek(pageX, cursor);
return false;
}
});
}
// methods involved in creating and cross-linking with HTML DOM nodes
/*
They all expect an .htmlTemplate like
'&0'
or
'&0&1'
See html.test.js for more examples.
Requirements:
- For each block of the command, there must be exactly one "block content
marker" of the form '&' where is the 0-based index of the
block. (Like the LaTeX \newcommand syntax, but with a 0-based rather than
1-based index, because JavaScript because C because Dijkstra.)
- The block content marker must be the sole contents of the containing
element, there can't even be surrounding whitespace, or else we can't
guarantee sticking to within the bounds of the block content marker when
mucking with the HTML DOM.
- The HTML not only must be well-formed HTML (of course), but also must
conform to the XHTML requirements on tags, specifically all tags must
either be self-closing (like '
') or come in matching pairs.
Close tags are never optional.
Note that & isn't well-formed HTML; if you wanted a literal '&123',
your HTML template would have to have '&123'.
*/
_.numBlocks = function() {
var matches = this.htmlTemplate.match(/&\d+/g);
return matches ? matches.length : 0;
};
_.html = function() {
// Render the entire math subtree rooted at this command, as HTML.
// Expects .createBlocks() to have been called already, since it uses the
// .blocks array of child blocks.
//
// See html.test.js for example templates and intended outputs.
//
// Given an .htmlTemplate as described above,
// - insert the mathquill-command-id attribute into all top-level tags,
// which will be used to set this.jQ in .jQize().
// This is straightforward:
// * tokenize into tags and non-tags
// * loop through top-level tokens:
// * add #cmdId attribute macro to top-level self-closing tags
// * else add #cmdId attribute macro to top-level open tags
// * skip the matching top-level close tag and all tag pairs
// in between
// - for each block content marker,
// + replace it with the contents of the corresponding block,
// rendered as HTML
// + insert the mathquill-block-id attribute into the containing tag
// This is even easier, a quick regex replace, since block tags cannot
// contain anything besides the block content marker.
//
// Two notes:
// - The outermost loop through top-level tokens should never encounter any
// top-level close tags, because we should have first encountered a
// matching top-level open tag, all inner tags should have appeared in
// matching pairs and been skipped, and then we should have skipped the
// close tag in question.
// - All open tags should have matching close tags, which means our inner
// loop should always encounter a close tag and drop nesting to 0. If
// a close tag is missing, the loop will continue until i >= tokens.length
// and token becomes undefined. This will not infinite loop, even in
// production without pray(), because it will then TypeError on .slice().
var cmd = this;
var blocks = cmd.blocks;
var cmdId = ' mathquill-command-id=' + cmd.id;
var tokens = cmd.htmlTemplate.match(/<[^<>]+>|[^<>]+/g);
pray('no unmatched angle brackets', tokens.join('') === this.htmlTemplate);
// add cmdId to all top-level tags
for (var i = 0, token = tokens[0]; token; i += 1, token = tokens[i]) {
// top-level self-closing tags
if (token.slice(-2) === '/>') {
tokens[i] = token.slice(0,-2) + cmdId + '/>';
}
// top-level open tags
else if (token.charAt(0) === '<') {
pray('not an unmatched top-level close tag', token.charAt(1) !== '/');
tokens[i] = token.slice(0,-1) + cmdId + '>';
// skip matching top-level close tag and all tag pairs in between
var nesting = 1;
do {
i += 1, token = tokens[i];
pray('no missing close tags', token);
// close tags
if (token.slice(0,2) === '') {
nesting -= 1;
}
// non-self-closing open tags
else if (token.charAt(0) === '<' && token.slice(-2) !== '/>') {
nesting += 1;
}
} while (nesting > 0);
}
}
return tokens.join('').replace(/>&(\d+)/g, function($0, $1) {
return ' mathquill-block-id=' + blocks[$1].id + '>' + blocks[$1].join('html');
});
};
// methods to export a string representation of the math tree
_.latex = function() {
return this.foldChildren(this.ctrlSeq, function(latex, child) {
return latex + '{' + (child.latex() || ' ') + '}';
});
};
_.textTemplate = [''];
_.text = function() {
var i = 0;
return this.foldChildren(this.textTemplate[i], function(text, child) {
i += 1;
var child_text = child.text();
if (text && this.textTemplate[i] === '('
&& child_text[0] === '(' && child_text.slice(-1) === ')')
return text + child_text.slice(1, -1) + this.textTemplate[i];
return text + child.text() + (this.textTemplate[i] || '');
});
};
});
/**
* Lightweight command without blocks or children.
*/
var Symbol = P(MathCommand, function(_, _super) {
_.init = function(ctrlSeq, html, text) {
if (!text) text = ctrlSeq && ctrlSeq.length > 1 ? ctrlSeq.slice(1) : ctrlSeq;
_super.init.call(this, ctrlSeq, html, [ text ]);
};
_.parser = function() { return Parser.succeed(this); };
_.numBlocks = function() { return 0; };
_.replaces = function(replacedFragment) {
replacedFragment.remove();
};
_.createBlocks = noop;
_.moveTowards = function(dir, cursor) {
cursor.jQ.insDirOf(dir, this.jQ);
cursor[-dir] = this;
cursor[dir] = this[dir];
};
_.deleteTowards = function(dir, cursor) {
cursor[dir] = this.remove()[dir];
};
_.seek = function(pageX, cursor) {
// insert at whichever side the click was closer to
if (pageX - this.jQ.offset().left < this.jQ.outerWidth()/2)
cursor.insLeftOf(this);
else
cursor.insRightOf(this);
};
_.latex = function(){ return this.ctrlSeq; };
_.text = function(){ return this.textTemplate; };
_.placeCursor = noop;
_.isEmpty = function(){ return true; };
});
/**
* Children and parent of MathCommand's. Basically partitions all the
* symbols and operators that descend (in the Math DOM tree) from
* ancestor operators.
*/
var MathBlock = P(MathElement, function(_) {
_.join = function(methodName) {
return this.foldChildren('', function(fold, child) {
return fold + child[methodName]();
});
};
_.html = function() { return this.join('html'); };
_.latex = function() { return this.join('latex'); };
_.text = function() {
return this.ends[L] === this.ends[R] ?
this.ends[L].text() :
'(' + this.join('text') + ')'
;
};
_.isEmpty = function() {
return this.ends[L] === 0 && this.ends[R] === 0;
};
// editability methods: called by the cursor for editing, cursor movements,
// and selection of the MathQuill tree, these all take in a direction and
// the cursor
_.moveOutOf = function(dir, cursor) {
if (this[dir]) cursor.insAtDirEnd(-dir, this[dir]);
else cursor.insDirOf(dir, this.parent);
};
_.selectOutOf = function(dir, cursor) {
cursor.insDirOf(dir, this.parent);
this.parent.selectedOutOf = this;
};
_.deleteOutOf = function(dir, cursor) {
cursor.unwrapGramp();
};
_.selectChildren = function(cursor, leftEnd, rightEnd) {
cursor.selection = Selection(leftEnd, rightEnd);
};
_.seek = function(pageX, cursor) {
var node = this.ends[R];
if (!node || node.jQ.offset().left + node.jQ.outerWidth() < pageX) {
return cursor.insAtRightEnd(this);
}
if (pageX < this.ends[L].jQ.offset().left) return cursor.insAtLeftEnd(this);
while (pageX < node.jQ.offset().left) node = node[L];
return node.seek(pageX, cursor);
};
_.write = function(cursor, ch, replacedFragment) {
var cmd;
if (ch.match(/^[a-eg-zA-Z]$/)) //exclude f because want florin
cmd = Variable(ch);
else if (cmd = CharCmds[ch] || LatexCmds[ch])
cmd = cmd(ch);
else
cmd = VanillaSymbol(ch);
if (replacedFragment) cmd.replaces(replacedFragment);
cmd.createBefore(cursor);
};
_.focus = function() {
this.jQ.addClass('hasCursor');
this.jQ.removeClass('empty');
return this;
};
_.blur = function() {
this.jQ.removeClass('hasCursor');
if (this.isEmpty())
this.jQ.addClass('empty');
return this;
};
});
/*********************************************
* Root math elements with event delegation.
********************************************/
function createRoot(jQ, root, textbox, editable) {
var contents = jQ.contents().detach();
if (!textbox) {
jQ.addClass('mathquill-rendered-math');
}
root.jQ = jQ.attr(mqBlockId, root.id);
root.revert = function() {
jQ.empty().unbind('.mathquill')
.removeClass('mathquill-rendered-math mathquill-editable mathquill-textbox')
.append(contents);
};
var cursor = root.cursor = Cursor(root);
root.renderLatex(contents.text());
//textarea stuff
var textareaSpan = root.textarea = $(''),
textarea = textareaSpan.children();
/******
* TODO [Han]: Document this
*/
var textareaSelectionTimeout;
root.selectionChanged = function() {
if (textareaSelectionTimeout === undefined) {
textareaSelectionTimeout = setTimeout(setTextareaSelection);
}
forceIERedraw(jQ[0]);
};
function setTextareaSelection() {
textareaSelectionTimeout = undefined;
var latex = '';
if (cursor.selection) {
latex = cursor.selection.fold('', function(latex, el) {
return latex + el.latex();
});
latex = '$' + latex + '$';
}
textareaManager.select(latex);
}
//prevent native selection except textarea
jQ.bind('selectstart.mathquill', function(e) {
if (e.target !== textarea[0]) e.preventDefault();
e.stopPropagation();
});
//drag-to-select event handling
var blink = cursor.blink;
jQ.bind('mousedown.mathquill', function(e) {
function mousemove(e) {
cursor.seek($(e.target), e.pageX, e.pageY).select();
// focus the least-common-ancestor block:
if (cursor.selection) cursor.insRightOf(cursor.selection.ends[R]);
return false;
}
// docmousemove is attached to the document, so that
// selection still works when the mouse leaves the window.
function docmousemove(e) {
// [Han]: i delete the target because of the way seek works.
// it will not move the mouse to the target, but will instead
// just seek those X and Y coordinates. If there is a target,
// it will try to move the cursor to document, which will not work.
// cursor.seek needs to be refactored.
delete e.target;
return mousemove(e);
}
function mouseup(e) {
cursor.endSelection();
cursor.blink = blink;
if (!cursor.selection) {
if (editable) {
cursor.show();
}
else {
textareaSpan.detach();
}
}
// delete the mouse handlers now that we're not dragging anymore
jQ.unbind('mousemove', mousemove);
$(e.target.ownerDocument).unbind('mousemove', docmousemove).unbind('mouseup', mouseup);
}
setTimeout(function() { textarea.focus(); });
// preventDefault won't prevent focus on mousedown in IE<9
// that means immediately after this mousedown, whatever was
// mousedown-ed will receive focus
// http://bugs.jquery.com/ticket/10345
cursor.blink = noop;
cursor.seek($(e.target), e.pageX, e.pageY).startSelection();
if (!editable) jQ.prepend(textareaSpan);
jQ.mousemove(mousemove);
$(e.target.ownerDocument).mousemove(docmousemove).mouseup(mouseup);
return false;
});
if (!editable) {
var textareaManager = manageTextarea(textarea, { container: jQ });
jQ.bind('cut paste', false).bind('copy', setTextareaSelection)
.prepend('$'+root.latex()+'$');
textarea.blur(function() {
cursor.clearSelection();
setTimeout(detach); //detaching during blur explodes in WebKit
});
function detach() {
textareaSpan.detach();
}
return;
}
var textareaManager = manageTextarea(textarea, {
container: jQ,
key: function(key, evt) {
cursor.parent.bubble('onKey', key, evt);
},
text: function(text) {
cursor.parent.bubble('onText', text);
},
cut: function(e) {
if (cursor.selection) {
setTimeout(function() {
cursor.prepareEdit();
cursor.parent.bubble('redraw');
});
}
e.stopPropagation();
},
paste: function(text) {
// FIXME HACK the parser in RootTextBlock needs to be moved to
// Cursor::writeLatex or something so this'll work with
// MathQuill textboxes
if (text.slice(0,1) === '$' && text.slice(-1) === '$') {
text = text.slice(1, -1);
}
else {
text = '\\text{' + text + '}';
}
cursor.writeLatex(text).show();
}
});
jQ.prepend(textareaSpan);
//root CSS classes
jQ.addClass('mathquill-editable');
if (textbox)
jQ.addClass('mathquill-textbox');
//focus and blur handling
textarea.focus(function(e) {
if (!cursor.parent)
cursor.insAtRightEnd(root);
cursor.parent.jQ.addClass('hasCursor');
if (cursor.selection) {
cursor.selection.jQ.removeClass('blur');
setTimeout(root.selectionChanged); //re-select textarea contents after tabbing away and back
}
else
cursor.show();
e.stopPropagation();
}).blur(function(e) {
cursor.hide().parent.blur();
if (cursor.selection)
cursor.selection.jQ.addClass('blur');
e.stopPropagation();
});
jQ.bind('focus.mathquill blur.mathquill', function(e) {
textarea.trigger(e);
}).blur();
}
var RootMathBlock = P(MathBlock, function(_, _super) {
_.latex = function() {
return _super.latex.call(this).replace(/(\\[a-z]+) (?![a-z])/ig,'$1');
};
_.text = function() {
return this.foldChildren('', function(text, child) {
return text + child.text();
});
};
_.renderLatex = function(latex) {
var jQ = this.jQ;
jQ.children().slice(1).remove();
this.ends[L] = this.ends[R] = 0;
this.cursor.insAtRightEnd(this).writeLatex(latex);
};
_.onKey = function(key, e) {
switch (key) {
case 'Ctrl-Shift-Backspace':
case 'Ctrl-Backspace':
while (this.cursor[L] || this.cursor.selection) {
this.cursor.backspace();
}
break;
case 'Shift-Backspace':
case 'Backspace':
this.cursor.backspace();
break;
// Tab or Esc -> go one block right if it exists, else escape right.
case 'Esc':
case 'Tab':
case 'Spacebar':
this.cursor.escapeDir(R, key, e);
return;
// Shift-Tab -> go one block left if it exists, else escape left.
case 'Shift-Tab':
case 'Shift-Esc':
case 'Shift-Spacebar':
this.cursor.escapeDir(L, key, e);
return;
// Prevent newlines from showing up
case 'Enter': break;
// End -> move to the end of the current block.
case 'End':
this.cursor.prepareMove().insAtRightEnd(this.cursor.parent);
break;
// Ctrl-End -> move all the way to the end of the root block.
case 'Ctrl-End':
this.cursor.prepareMove().insAtRightEnd(this);
break;
// Shift-End -> select to the end of the current block.
case 'Shift-End':
while (this.cursor[R]) {
this.cursor.selectRight();
}
break;
// Ctrl-Shift-End -> select to the end of the root block.
case 'Ctrl-Shift-End':
while (this.cursor[R] || this.cursor.parent !== this) {
this.cursor.selectRight();
}
break;
// Home -> move to the start of the root block or the current block.
case 'Home':
this.cursor.prepareMove().insAtLeftEnd(this.cursor.parent);
break;
// Ctrl-Home -> move to the start of the current block.
case 'Ctrl-Home':
this.cursor.prepareMove().insAtLeftEnd(this);
break;
// Shift-Home -> select to the start of the current block.
case 'Shift-Home':
while (this.cursor[L]) {
this.cursor.selectLeft();
}
break;
// Ctrl-Shift-Home -> move to the start of the root block.
case 'Ctrl-Shift-Home':
while (this.cursor[L] || this.cursor.parent !== this) {
this.cursor.selectLeft();
}
break;
case 'Left': this.cursor.moveLeft(); break;
case 'Shift-Left': this.cursor.selectLeft(); break;
case 'Ctrl-Left': break;
case 'Right': this.cursor.moveRight(); break;
case 'Shift-Right': this.cursor.selectRight(); break;
case 'Ctrl-Right': break;
case 'Up': this.cursor.moveUp(); break;
case 'Down': this.cursor.moveDown(); break;
case 'Shift-Up':
if (this.cursor[L]) {
while (this.cursor[L]) this.cursor.selectLeft();
} else {
this.cursor.selectLeft();
}
case 'Shift-Down':
if (this.cursor[R]) {
while (this.cursor[R]) this.cursor.selectRight();
}
else {
this.cursor.selectRight();
}
case 'Ctrl-Up': break;
case 'Ctrl-Down': break;
case 'Ctrl-Shift-Del':
case 'Ctrl-Del':
while (this.cursor[R] || this.cursor.selection) {
this.cursor.deleteForward();
}
break;
case 'Shift-Del':
case 'Del':
this.cursor.deleteForward();
break;
case 'Meta-A':
case 'Ctrl-A':
//so not stopPropagation'd at RootMathCommand
if (this !== this.cursor.root) return;
this.cursor.prepareMove().insAtRightEnd(this);
while (this.cursor[L]) this.cursor.selectLeft();
break;
default:
return false;
}
e.preventDefault();
return false;
};
_.onText = function(ch) {
this.cursor.write(ch);
return false;
};
});
var RootMathCommand = P(MathCommand, function(_, _super) {
_.init = function(cursor) {
_super.init.call(this, '$');
this.cursor = cursor;
};
_.htmlTemplate = '&0';
_.createBlocks = function() {
this.ends[L] =
this.ends[R] =
RootMathBlock();
this.blocks = [ this.ends[L] ];
this.ends[L].parent = this;
this.ends[L].cursor = this.cursor;
this.ends[L].write = function(cursor, ch, replacedFragment) {
if (ch !== '$')
MathBlock.prototype.write.call(this, cursor, ch, replacedFragment);
else if (this.isEmpty()) {
cursor.insRightOf(this.parent).backspace().show();
VanillaSymbol('\\$','$').createBefore(cursor);
}
else if (!cursor[R])
cursor.insRightOf(this.parent);
else if (!cursor[L])
cursor.insLeftOf(this.parent);
else
MathBlock.prototype.write.call(this, cursor, ch, replacedFragment);
};
};
_.latex = function() {
return '$' + this.ends[L].latex() + '$';
};
});
var RootTextBlock = P(MathBlock, function(_) {
_.renderLatex = function(latex) {
var self = this;
var cursor = self.cursor;
self.jQ.children().slice(1).remove();
self.ends[L] = self.ends[R] = 0;
cursor.show().insAtRightEnd(self);
var regex = Parser.regex;
var string = Parser.string;
var eof = Parser.eof;
var all = Parser.all;
// Parser RootMathCommand
var mathMode = string('$').then(latexMathParser)
// because TeX is insane, math mode doesn't necessarily
// have to end. So we allow for the case that math mode
// continues to the end of the stream.
.skip(string('$').or(eof))
.map(function(block) {
// HACK FIXME: this shouldn't have to have access to cursor
var rootMathCommand = RootMathCommand(cursor);
rootMathCommand.createBlocks();
var rootMathBlock = rootMathCommand.ends[L];
block.children().adopt(rootMathBlock, 0, 0);
return rootMathCommand;
})
;
var escapedDollar = string('\\$').result('$');
var textChar = escapedDollar.or(regex(/^[^$]/)).map(VanillaSymbol);
var latexText = mathMode.or(textChar).many();
var commands = latexText.skip(eof).or(all.result(false)).parse(latex);
if (commands) {
for (var i = 0; i < commands.length; i += 1) {
commands[i].adopt(self, self.ends[R], 0);
}
self.jQize().appendTo(self.jQ);
self.finalizeInsert();
}
};
_.onKey = function(key) {
if (key === 'Spacebar' || key === 'Shift-Spacebar') return;
RootMathBlock.prototype.onKey.apply(this, arguments);
};
_.onText = RootMathBlock.prototype.onText;
_.write = function(cursor, ch, replacedFragment) {
if (replacedFragment) replacedFragment.remove();
if (ch === '$')
RootMathCommand(cursor).createBefore(cursor);
else {
var html;
if (ch === '<') html = '<';
else if (ch === '>') html = '>';
VanillaSymbol(ch, html).createBefore(cursor);
}
};
});
/***************************
* Commands and Operators.
**************************/
var CharCmds = {}, LatexCmds = {}; //single character commands, LaTeX commands
var scale, // = function(jQ, x, y) { ... }
//will use a CSS 2D transform to scale the jQuery-wrapped HTML elements,
//or the filter matrix transform fallback for IE 5.5-8, or gracefully degrade to
//increasing the fontSize to match the vertical Y scaling factor.
//ideas from http://github.com/louisremi/jquery.transform.js
//see also http://msdn.microsoft.com/en-us/library/ms533014(v=vs.85).aspx
forceIERedraw = noop,
div = document.createElement('div'),
div_style = div.style,
transformPropNames = {
transform:1,
WebkitTransform:1,
MozTransform:1,
OTransform:1,
msTransform:1
},
transformPropName;
for (var prop in transformPropNames) {
if (prop in div_style) {
transformPropName = prop;
break;
}
}
if (transformPropName) {
scale = function(jQ, x, y) {
jQ.css(transformPropName, 'scale('+x+','+y+')');
};
}
else if ('filter' in div_style) { //IE 6, 7, & 8 fallback, see https://github.com/laughinghan/mathquill/wiki/Transforms
forceIERedraw = function(el){ el.className = el.className; };
scale = function(jQ, x, y) { //NOTE: assumes y > x
x /= (1+(y-1)/2);
jQ.css('fontSize', y + 'em');
if (!jQ.hasClass('matrixed-container')) {
jQ.addClass('matrixed-container')
.wrapInner('');
}
var innerjQ = jQ.children()
.css('filter', 'progid:DXImageTransform.Microsoft'
+ '.Matrix(M11=' + x + ",SizingMethod='auto expand')"
);
function calculateMarginRight() {
jQ.css('marginRight', (innerjQ.width()-1)*(x-1)/x + 'px');
}
calculateMarginRight();
var intervalId = setInterval(calculateMarginRight);
$(window).load(function() {
clearTimeout(intervalId);
calculateMarginRight();
});
};
}
else {
scale = function(jQ, x, y) {
jQ.css('fontSize', y + 'em');
};
}
var Style = P(MathCommand, function(_, _super) {
_.init = function(ctrlSeq, tagName, attrs) {
_super.init.call(this, ctrlSeq, '<'+tagName+' '+attrs+'>&0'+tagName+'>');
};
});
//fonts
LatexCmds.mathrm = bind(Style, '\\mathrm', 'span', 'class="roman font"');
LatexCmds.mathit = bind(Style, '\\mathit', 'i', 'class="font"');
LatexCmds.mathbf = bind(Style, '\\mathbf', 'b', 'class="font"');
LatexCmds.mathsf = bind(Style, '\\mathsf', 'span', 'class="sans-serif font"');
LatexCmds.mathtt = bind(Style, '\\mathtt', 'span', 'class="monospace font"');
//text-decoration
LatexCmds.underline = bind(Style, '\\underline', 'span', 'class="non-leaf underline"');
LatexCmds.overline = LatexCmds.bar = bind(Style, '\\overline', 'span', 'class="non-leaf overline"');
// `\textcolor{color}{math}` will apply a color to the given math content, where
// `color` is any valid CSS Color Value (see [SitePoint docs][] (recommended),
// [Mozilla docs][], or [W3C spec][]).
//
// [SitePoint docs]: http://reference.sitepoint.com/css/colorvalues
// [Mozilla docs]: https://developer.mozilla.org/en-US/docs/CSS/color_value#Values
// [W3C spec]: http://dev.w3.org/csswg/css3-color/#colorunits
var TextColor = LatexCmds.textcolor = P(MathCommand, function(_, _super) {
_.htmlTemplate = '&0';
_.jQadd = function() {
_super.jQadd.apply(this, arguments);
this.jQ.css('color', this.color);
};
_.parser = function() {
var self = this;
var optWhitespace = Parser.optWhitespace;
var string = Parser.string;
var regex = Parser.regex;
return optWhitespace
.then(string('{'))
.then(regex(/^[^{}]*/))
.skip(string('}'))
.then(function(color) {
self.color = color;
return _super.parser.call(self);
})
;
};
});
var SupSub = P(MathCommand, function(_, _super) {
_.init = function(ctrlSeq, tag, text) {
_super.init.call(this, ctrlSeq, '<'+tag+' class="non-leaf">&0'+tag+'>', [ text ]);
};
_.finalizeTree = function() {
//TODO: use inheritance
pray('SupSub is only _ and ^',
this.ctrlSeq === '^' || this.ctrlSeq === '_'
);
if (this.ctrlSeq === '_') {
this.downInto = this.ends[L];
this.ends[L].upOutOf = insLeftOfMeUnlessAtEnd;
}
else {
this.upInto = this.ends[L];
this.ends[L].downOutOf = insLeftOfMeUnlessAtEnd;
}
function insLeftOfMeUnlessAtEnd(cursor) {
// cursor.insLeftOf(cmd), unless cursor at the end of block, and every
// ancestor cmd is at the end of every ancestor block
var cmd = this.parent, ancestorCmd = cursor;
do {
if (ancestorCmd[R]) {
cursor.insLeftOf(cmd);
return false;
}
ancestorCmd = ancestorCmd.parent.parent;
} while (ancestorCmd !== cmd);
cursor.insRightOf(cmd);
return false;
}
};
_.latex = function() {
var latex = this.ends[L].latex();
if (latex.length === 1)
return this.ctrlSeq + latex;
else
return this.ctrlSeq + '{' + (latex || ' ') + '}';
};
_.redraw = function() {
if (this[L])
this[L].respace();
//SupSub::respace recursively calls respace on all the following SupSubs
//so if leftward is a SupSub, no need to call respace on this or following nodes
if (!(this[L] instanceof SupSub)) {
this.respace();
//and if rightward is a SupSub, then this.respace() will have already called
//this[R].respace()
if (this[R] && !(this[R] instanceof SupSub))
this[R].respace();
}
};
_.respace = function() {
if (
this[L].ctrlSeq === '\\int ' || (
this[L] instanceof SupSub && this[L].ctrlSeq != this.ctrlSeq
&& this[L][L] && this[L][L].ctrlSeq === '\\int '
)
) {
if (!this.limit) {
this.limit = true;
this.jQ.addClass('limit');
}
}
else {
if (this.limit) {
this.limit = false;
this.jQ.removeClass('limit');
}
}
this.respaced = this[L] instanceof SupSub && this[L].ctrlSeq != this.ctrlSeq && !this[L].respaced;
if (this.respaced) {
var fontSize = +this.jQ.css('fontSize').slice(0,-2),
leftWidth = this[L].jQ.outerWidth(),
thisWidth = this.jQ.outerWidth();
this.jQ.css({
left: (this.limit && this.ctrlSeq === '_' ? -.25 : 0) - leftWidth/fontSize + 'em',
marginRight: .1 - min(thisWidth, leftWidth)/fontSize + 'em'
//1px extra so it doesn't wrap in retarded browsers (Firefox 2, I think)
});
}
else if (this.limit && this.ctrlSeq === '_') {
this.jQ.css({
left: '-.25em',
marginRight: ''
});
}
else {
this.jQ.css({
left: '',
marginRight: ''
});
}
if (this[R] instanceof SupSub)
this[R].respace();
return this;
};
});
LatexCmds.subscript =
LatexCmds._ = bind(SupSub, '_', 'sub', '_');
LatexCmds.superscript =
LatexCmds.supscript =
LatexCmds['^'] = bind(SupSub, '^', 'sup', '**');
var Fraction =
LatexCmds.frac =
LatexCmds.dfrac =
LatexCmds.cfrac =
LatexCmds.fraction = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\frac';
_.htmlTemplate =
''
+ '&0'
+ '&1'
+ ' '
+ ''
;
_.textTemplate = ['(', '/', ')'];
_.finalizeTree = function() {
this.upInto = this.ends[R].upOutOf = this.ends[L];
this.downInto = this.ends[L].downOutOf = this.ends[R];
};
});
var LiveFraction =
LatexCmds.over =
CharCmds['/'] = P(Fraction, function(_, _super) {
_.createBefore = function(cursor) {
if (!this.replacedFragment) {
var leftward = cursor[L];
while (leftward &&
!(
leftward instanceof BinaryOperator ||
leftward instanceof TextBlock ||
leftward instanceof BigSymbol ||
',;:'.split('').indexOf(leftward.ctrlSeq) > -1
) //lookbehind for operator
) leftward = leftward[L];
if (leftward instanceof BigSymbol && leftward[R] instanceof SupSub) {
leftward = leftward[R];
if (leftward[R] instanceof SupSub && leftward[R].ctrlSeq != leftward.ctrlSeq)
leftward = leftward[R];
}
if (leftward !== cursor[L]) {
this.replaces(Fragment(leftward[R] || cursor.parent.ends[L], cursor[L]));
cursor[L] = leftward;
}
}
_super.createBefore.call(this, cursor);
};
});
var SquareRoot =
LatexCmds.sqrt =
LatexCmds['√'] = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\sqrt';
_.htmlTemplate =
''
+ '√'
+ '&0'
+ ''
;
_.textTemplate = ['sqrt(', ')'];
_.parser = function() {
return latexMathParser.optBlock.then(function(optBlock) {
return latexMathParser.block.map(function(block) {
var nthroot = NthRoot();
nthroot.blocks = [ optBlock, block ];
optBlock.adopt(nthroot, 0, 0);
block.adopt(nthroot, optBlock, 0);
return nthroot;
});
}).or(_super.parser.call(this));
};
_.redraw = function() {
var block = this.ends[R].jQ;
scale(block.prev(), 1, block.innerHeight()/+block.css('fontSize').slice(0,-2) - .1);
};
});
var NthRoot =
LatexCmds.nthroot = P(SquareRoot, function(_, _super) {
_.htmlTemplate =
'&0'
+ ''
+ '√'
+ '&1'
+ ''
;
_.textTemplate = ['sqrt[', '](', ')'];
_.latex = function() {
return '\\sqrt['+this.ends[L].latex()+']{'+this.ends[R].latex()+'}';
};
});
// Round/Square/Curly/Angle Brackets (aka Parens/Brackets/Braces)
var Bracket = P(MathCommand, function(_, _super) {
_.init = function(open, close, ctrlSeq, end) {
_super.init.call(this, '\\left'+ctrlSeq,
''
+ ''+open+''
+ '&0'
+ ''+close+''
+ '',
[open, close]);
this.end = '\\right'+end;
};
_.jQadd = function() {
_super.jQadd.apply(this, arguments);
var jQ = this.jQ;
this.bracketjQs = jQ.children(':first').add(jQ.children(':last'));
};
_.latex = function() {
return this.ctrlSeq + this.ends[L].latex() + this.end;
};
_.redraw = function() {
var blockjQ = this.ends[L].jQ;
var height = blockjQ.outerHeight()/+blockjQ.css('fontSize').slice(0,-2);
scale(this.bracketjQs, min(1 + .2*(height - 1), 1.2), 1.05*height);
};
});
LatexCmds.left = P(MathCommand, function(_) {
_.parser = function() {
var regex = Parser.regex;
var string = Parser.string;
var succeed = Parser.succeed;
var optWhitespace = Parser.optWhitespace;
return optWhitespace.then(regex(/^(?:[([|]|\\\{)/))
.then(function(open) {
if (open.charAt(0) === '\\') open = open.slice(1);
var cmd = CharCmds[open]();
return latexMathParser
.map(function (block) {
cmd.blocks = [ block ];
block.adopt(cmd, 0, 0);
})
.then(string('\\right'))
.skip(optWhitespace)
.then(regex(/^(?:[\])|]|\\\})/))
.then(function(close) {
if (close.slice(-1) !== cmd.end.slice(-1)) {
return Parser.fail('open doesn\'t match close');
}
return succeed(cmd);
})
;
})
;
};
});
LatexCmds.right = P(MathCommand, function(_) {
_.parser = function() {
return Parser.fail('unmatched \\right');
};
});
LatexCmds.lbrace =
CharCmds['{'] = bind(Bracket, '{', '}', '\\{', '\\}');
LatexCmds.langle =
LatexCmds.lang = bind(Bracket, '〈','〉','\\langle ','\\rangle ');
// Closing bracket matching opening bracket above
var CloseBracket = P(Bracket, function(_, _super) {
_.createBefore = function(cursor) {
// if I'm at the end of my parent who is a matching open-paren,
// and I am not replacing a selection fragment, don't create me,
// just put cursor after my parent
if (!cursor[R] && cursor.parent.parent && cursor.parent.parent.end === this.end && !this.replacedFragment)
cursor.insRightOf(cursor.parent.parent);
else
_super.createBefore.call(this, cursor);
};
_.placeCursor = function(cursor) {
this.ends[L].blur();
cursor.insRightOf(this);
};
});
LatexCmds.rbrace =
CharCmds['}'] = bind(CloseBracket, '{','}','\\{','\\}');
LatexCmds.rangle =
LatexCmds.rang = bind(CloseBracket, '〈','〉','\\langle ','\\rangle ');
var parenMixin = function(_, _super) {
_.init = function(open, close) {
_super.init.call(this, open, close, open, close);
};
};
var Paren = P(Bracket, parenMixin);
LatexCmds.lparen =
CharCmds['('] = bind(Paren, '(', ')');
LatexCmds.lbrack =
LatexCmds.lbracket =
CharCmds['['] = bind(Paren, '[', ']');
var CloseParen = P(CloseBracket, parenMixin);
LatexCmds.rparen =
CharCmds[')'] = bind(CloseParen, '(', ')');
LatexCmds.rbrack =
LatexCmds.rbracket =
CharCmds[']'] = bind(CloseParen, '[', ']');
var Pipes =
LatexCmds.lpipe =
LatexCmds.rpipe =
CharCmds['|'] = P(Paren, function(_, _super) {
_.init = function() {
_super.init.call(this, '|', '|');
};
_.createBefore = CloseBracket.prototype.createBefore;
});
// input box to type a variety of LaTeX commands beginning with a backslash
var LatexCommandInput =
CharCmds['\\'] = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\';
_.replaces = function(replacedFragment) {
this._replacedFragment = replacedFragment.disown();
this.isEmpty = function() { return false; };
};
_.htmlTemplate = '\\&0';
_.textTemplate = ['\\'];
_.createBlocks = function() {
_super.createBlocks.call(this);
this.ends[L].focus = function() {
this.parent.jQ.addClass('hasCursor');
if (this.isEmpty())
this.parent.jQ.removeClass('empty');
return this;
};
this.ends[L].blur = function() {
this.parent.jQ.removeClass('hasCursor');
if (this.isEmpty())
this.parent.jQ.addClass('empty');
return this;
};
};
_.createBefore = function(cursor) {
_super.createBefore.call(this, cursor);
this.cursor = cursor.insAtRightEnd(this.ends[L]);
if (this._replacedFragment) {
var el = this.jQ[0];
this.jQ =
this._replacedFragment.jQ.addClass('blur').bind(
'mousedown mousemove', //FIXME: is monkey-patching the mousedown and mousemove handlers the right way to do this?
function(e) {
$(e.target = el).trigger(e);
return false;
}
).insertBefore(this.jQ).add(this.jQ);
}
this.ends[L].write = function(cursor, ch, replacedFragment) {
if (replacedFragment) replacedFragment.remove();
if (ch.match(/[a-z]/i)) VanillaSymbol(ch).createBefore(cursor);
else {
this.parent.renderCommand();
if (ch !== '\\' || !this.isEmpty()) this.parent.parent.write(cursor, ch);
}
};
};
_.latex = function() {
return '\\' + this.ends[L].latex() + ' ';
};
_.onKey = function(key, e) {
if (key === 'Tab' || key === 'Enter' || key === 'Spacebar') {
this.renderCommand();
e.preventDefault();
return false;
}
};
_.renderCommand = function() {
this.jQ = this.jQ.last();
this.remove();
if (this[R]) {
this.cursor.insLeftOf(this[R]);
} else {
this.cursor.insAtRightEnd(this.parent);
}
var latex = this.ends[L].latex();
if (!latex) latex = 'backslash';
this.cursor.insertCmd(latex, this._replacedFragment);
};
});
var Binomial =
LatexCmds.binom =
LatexCmds.binomial = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\binom';
_.htmlTemplate =
'('
+ ''
+ ''
+ '&0'
+ '&1'
+ ''
+ ''
+ ')'
;
_.textTemplate = ['choose(',',',')'];
_.redraw = function() {
var blockjQ = this.jQ.eq(1);
var height = blockjQ.outerHeight()/+blockjQ.css('fontSize').slice(0,-2);
var parens = this.jQ.filter('.paren');
scale(parens, min(1 + .2*(height - 1), 1.2), 1.05*height);
};
});
var Choose =
LatexCmds.choose = P(Binomial, function(_) {
_.createBefore = LiveFraction.prototype.createBefore;
});
var Vector =
LatexCmds.vector = P(MathCommand, function(_, _super) {
_.ctrlSeq = '\\vector';
_.htmlTemplate = '&0';
_.latex = function() {
return '\\begin{matrix}' + this.foldChildren([], function(latex, child) {
latex.push(child.latex());
return latex;
}).join('\\\\') + '\\end{matrix}';
};
_.text = function() {
return '[' + this.foldChildren([], function(text, child) {
text.push(child.text());
return text;
}).join() + ']';
};
_.createBefore = function(cursor) {
_super.createBefore.call(this, this.cursor = cursor);
};
_.onKey = function(key, e) {
var currentBlock = this.cursor.parent;
if (currentBlock.parent === this) {
if (key === 'Enter') { //enter
var newBlock = MathBlock();
newBlock.parent = this;
newBlock.jQ = $('')
.attr(mqBlockId, newBlock.id)
.insertAfter(currentBlock.jQ);
if (currentBlock[R])
currentBlock[R][L] = newBlock;
else
this.ends[R] = newBlock;
newBlock[R] = currentBlock[R];
currentBlock[R] = newBlock;
newBlock[L] = currentBlock;
this.bubble('redraw').cursor.insAtRightEnd(newBlock);
e.preventDefault();
return false;
}
else if (key === 'Tab' && !currentBlock[R]) {
if (currentBlock.isEmpty()) {
if (currentBlock[L]) {
this.cursor.insRightOf(this);
delete currentBlock[L][R];
this.ends[R] = currentBlock[L];
currentBlock.jQ.remove();
this.bubble('redraw');
e.preventDefault();
return false;
}
else
return;
}
var newBlock = MathBlock();
newBlock.parent = this;
newBlock.jQ = $('').attr(mqBlockId, newBlock.id).appendTo(this.jQ);
this.ends[R] = newBlock;
currentBlock[R] = newBlock;
newBlock[L] = currentBlock;
this.bubble('redraw').cursor.insAtRightEnd(newBlock);
e.preventDefault();
return false;
}
else if (e.which === 8) { //backspace
if (currentBlock.isEmpty()) {
if (currentBlock[L]) {
this.cursor.insAtRightEnd(currentBlock[L]);
currentBlock[L][R] = currentBlock[R];
}
else {
this.cursor.insLeftOf(this);
this.ends[L] = currentBlock[R];
}
if (currentBlock[R])
currentBlock[R][L] = currentBlock[L];
else
this.ends[R] = currentBlock[L];
currentBlock.jQ.remove();
if (this.isEmpty())
this.cursor.deleteForward();
else
this.bubble('redraw');
e.preventDefault();
return false;
}
else if (!this.cursor[L]) {
e.preventDefault();
return false;
}
}
}
};
});
LatexCmds.editable = P(RootMathCommand, function(_, _super) {
_.init = function() {
MathCommand.prototype.init.call(this, '\\editable');
};
_.jQadd = function() {
var self = this;
// FIXME: this entire method is a giant hack to get around
// having to call createBlocks, and createRoot expecting to
// render the contents' LaTeX. Both need to be refactored.
_super.jQadd.apply(self, arguments);
var block = self.ends[L].disown();
var blockjQ = self.jQ.children().detach();
self.ends[L] =
self.ends[R] =
RootMathBlock();
self.blocks = [ self.ends[L] ];
self.ends[L].parent = self;
createRoot(self.jQ, self.ends[L], false, true);
self.cursor = self.ends[L].cursor;
block.children().adopt(self.ends[L], 0, 0);
blockjQ.appendTo(self.ends[L].jQ);
self.ends[L].cursor.insAtRightEnd(self.ends[L]);
};
_.latex = function(){ return this.ends[L].latex(); };
_.text = function(){ return this.ends[L].text(); };
});
/**********************************
* Symbols and Special Characters
*********************************/
LatexCmds.f = bind(Symbol, 'f', 'ƒ ');
var Variable = P(Symbol, function(_, _super) {
_.init = function(ch, html) {
_super.init.call(this, ch, ''+(html || ch)+'');
};
_.text = function() {
var text = this.ctrlSeq;
if (this[L] && !(this[L] instanceof Variable)
&& !(this[L] instanceof BinaryOperator))
text = '*' + text;
if (this[R] && !(this[R] instanceof BinaryOperator)
&& !(this[R].ctrlSeq === '^'))
text += '*';
return text;
};
});
var VanillaSymbol = P(Symbol, function(_, _super) {
_.init = function(ch, html) {
_super.init.call(this, ch, ''+(html || ch)+'');
};
});
CharCmds[' '] = bind(VanillaSymbol, '\\:', ' ');
LatexCmds.prime = CharCmds["'"] = bind(VanillaSymbol, "'", '′');
// does not use Symbola font
var NonSymbolaSymbol = P(Symbol, function(_, _super) {
_.init = function(ch, html) {
_super.init.call(this, ch, ''+(html || ch)+'');
};
});
LatexCmds['@'] = NonSymbolaSymbol;
LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&');
LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%');
//the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html
//lowercase Greek letter variables
LatexCmds.alpha =
LatexCmds.beta =
LatexCmds.gamma =
LatexCmds.delta =
LatexCmds.zeta =
LatexCmds.eta =
LatexCmds.theta =
LatexCmds.iota =
LatexCmds.kappa =
LatexCmds.mu =
LatexCmds.nu =
LatexCmds.xi =
LatexCmds.rho =
LatexCmds.sigma =
LatexCmds.tau =
LatexCmds.chi =
LatexCmds.psi =
LatexCmds.omega = P(Variable, function(_, _super) {
_.init = function(latex) {
_super.init.call(this,'\\'+latex+' ','&'+latex+';');
};
});
//why can't anybody FUCKING agree on these
LatexCmds.phi = //W3C or Unicode?
bind(Variable,'\\phi ','ϕ');
LatexCmds.phiv = //Elsevier and 9573-13
LatexCmds.varphi = //AMS and LaTeX
bind(Variable,'\\varphi ','φ');
LatexCmds.epsilon = //W3C or Unicode?
bind(Variable,'\\epsilon ','ϵ');
LatexCmds.epsiv = //Elsevier and 9573-13
LatexCmds.varepsilon = //AMS and LaTeX
bind(Variable,'\\varepsilon ','ε');
LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13
LatexCmds.varpi = //AMS and LaTeX
bind(Variable,'\\varpi ','ϖ');
LatexCmds.sigmaf = //W3C/Unicode
LatexCmds.sigmav = //Elsevier
LatexCmds.varsigma = //LaTeX
bind(Variable,'\\varsigma ','ς');
LatexCmds.thetav = //Elsevier and 9573-13
LatexCmds.vartheta = //AMS and LaTeX
LatexCmds.thetasym = //W3C/Unicode
bind(Variable,'\\vartheta ','ϑ');
LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode
LatexCmds.upsi = //Elsevier and 9573-13
bind(Variable,'\\upsilon ','υ');
//these aren't even mentioned in the HTML character entity references
LatexCmds.gammad = //Elsevier
LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above)
LatexCmds.digamma = //LaTeX
bind(Variable,'\\digamma ','ϝ');
LatexCmds.kappav = //Elsevier
LatexCmds.varkappa = //AMS and LaTeX
bind(Variable,'\\varkappa ','ϰ');
LatexCmds.rhov = //Elsevier and 9573-13
LatexCmds.varrho = //AMS and LaTeX
bind(Variable,'\\varrho ','ϱ');
//Greek constants, look best in un-italicised Times New Roman
LatexCmds.pi = LatexCmds['π'] = bind(NonSymbolaSymbol,'\\pi ','π');
LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','λ');
//uppercase greek letters
LatexCmds.Upsilon = //LaTeX
LatexCmds.Upsi = //Elsevier and 9573-13
LatexCmds.upsih = //W3C/Unicode "upsilon with hook"
LatexCmds.Upsih = //'cos it makes sense to me
bind(Symbol,'\\Upsilon ','ϒ'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :(
//other symbols with the same LaTeX command and HTML character entity reference
LatexCmds.Gamma =
LatexCmds.Delta =
LatexCmds.Theta =
LatexCmds.Lambda =
LatexCmds.Xi =
LatexCmds.Pi =
LatexCmds.Sigma =
LatexCmds.Phi =
LatexCmds.Psi =
LatexCmds.Omega =
LatexCmds.forall = P(VanillaSymbol, function(_, _super) {
_.init = function(latex) {
_super.init.call(this,'\\'+latex+' ','&'+latex+';');
};
});
// symbols that aren't a single MathCommand, but are instead a whole
// Fragment. Creates the Fragment from a LaTeX string
var LatexFragment = P(MathCommand, function(_) {
_.init = function(latex) { this.latex = latex; };
_.createBefore = function(cursor) { cursor.writeLatex(this.latex); };
_.parser = function() {
var frag = latexMathParser.parse(this.latex).children();
return Parser.succeed(frag);
};
});
// for what seems to me like [stupid reasons][1], Unicode provides
// subscripted and superscripted versions of all ten Arabic numerals,
// as well as [so-called "vulgar fractions"][2].
// Nobody really cares about most of them, but some of them actually
// predate Unicode, dating back to [ISO-8859-1][3], apparently also
// known as "Latin-1", which among other things [Windows-1252][4]
// largely coincides with, so Microsoft Word sometimes inserts them
// and they get copy-pasted into MathQuill.
//
// (Irrelevant but funny story: Windows-1252 is actually a strict
// superset of the "closely related but distinct"[3] "ISO 8859-1" --
// see the lack of a dash after "ISO"? Completely different character
// set, like elephants vs elephant seals, or "Zombies" vs "Zombie
// Redneck Torture Family". What kind of idiot would get them confused.
// People in fact got them confused so much, it was so common to
// mislabel Windows-1252 text as ISO-8859-1, that most modern web
// browsers and email clients treat the MIME charset of ISO-8859-1
// as actually Windows-1252, behavior now standard in the HTML5 spec.)
//
// [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts
// [2]: http://en.wikipedia.org/wiki/Number_Forms
// [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1
// [4]: http://en.wikipedia.org/wiki/Windows-1252
LatexCmds['¹'] = bind(LatexFragment, '^1');
LatexCmds['²'] = bind(LatexFragment, '^2');
LatexCmds['³'] = bind(LatexFragment, '^3');
LatexCmds['¼'] = bind(LatexFragment, '\\frac14');
LatexCmds['½'] = bind(LatexFragment, '\\frac12');
LatexCmds['¾'] = bind(LatexFragment, '\\frac34');
var BinaryOperator = P(Symbol, function(_, _super) {
_.init = function(ctrlSeq, html, text) {
_super.init.call(this,
ctrlSeq, ''+html+'', text
);
};
});
var PlusMinus = P(BinaryOperator, function(_) {
_.init = VanillaSymbol.prototype.init;
_.respace = function() {
if (!this[L]) {
this.jQ[0].className = '';
}
else if (
this[L] instanceof BinaryOperator &&
this[R] && !(this[R] instanceof BinaryOperator)
) {
this.jQ[0].className = 'unary-operator';
}
else {
this.jQ[0].className = 'binary-operator';
}
return this;
};
});
LatexCmds['+'] = bind(PlusMinus, '+', '+');
//yes, these are different dashes, I think one is an en dash and the other is a hyphen
LatexCmds['–'] = LatexCmds['-'] = bind(PlusMinus, '-', '−');
LatexCmds['±'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus =
bind(PlusMinus,'\\pm ','±');
LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus =
bind(PlusMinus,'\\mp ','∓');
CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
bind(BinaryOperator, '\\cdot ', '·');
//semantically should be ⋅, but · looks better
LatexCmds['='] = bind(BinaryOperator, '=', '=');
LatexCmds['<'] = bind(BinaryOperator, '<', '<');
LatexCmds['>'] = bind(BinaryOperator, '>', '>');
LatexCmds.notin =
LatexCmds.sim =
LatexCmds.cong =
LatexCmds.equiv =
LatexCmds.oplus =
LatexCmds.otimes = P(BinaryOperator, function(_, _super) {
_.init = function(latex) {
_super.init.call(this, '\\'+latex+' ', '&'+latex+';');
};
});
LatexCmds.times = bind(BinaryOperator, '\\times ', '×', '[x]');
LatexCmds['÷'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides =
bind(BinaryOperator,'\\div ','÷', '[/]');
LatexCmds['≠'] = LatexCmds.ne = LatexCmds.neq = bind(BinaryOperator,'\\ne ','≠');
LatexCmds.ast = LatexCmds.star = LatexCmds.loast = LatexCmds.lowast =
bind(BinaryOperator,'\\ast ','∗');
//case 'there4 = // a special exception for this one, perhaps?
LatexCmds.therefor = LatexCmds.therefore =
bind(BinaryOperator,'\\therefore ','∴');
LatexCmds.cuz = // l33t
LatexCmds.because = bind(BinaryOperator,'\\because ','∵');
LatexCmds.prop = LatexCmds.propto = bind(BinaryOperator,'\\propto ','∝');
LatexCmds['≈'] = LatexCmds.asymp = LatexCmds.approx = bind(BinaryOperator,'\\approx ','≈');
LatexCmds.lt = bind(BinaryOperator,'<','<');
LatexCmds.gt = bind(BinaryOperator,'>','>');
LatexCmds['≤'] = LatexCmds.le = LatexCmds.leq = bind(BinaryOperator,'\\le ','≤');
LatexCmds['≥'] = LatexCmds.ge = LatexCmds.geq = bind(BinaryOperator,'\\ge ','≥');
LatexCmds.isin = LatexCmds['in'] = bind(BinaryOperator,'\\in ','∈');
LatexCmds.ni = LatexCmds.contains = bind(BinaryOperator,'\\ni ','∋');
LatexCmds.notni = LatexCmds.niton = LatexCmds.notcontains = LatexCmds.doesnotcontain =
bind(BinaryOperator,'\\not\\ni ','∌');
LatexCmds.sub = LatexCmds.subset = bind(BinaryOperator,'\\subset ','⊂');
LatexCmds.sup = LatexCmds.supset = LatexCmds.superset =
bind(BinaryOperator,'\\supset ','⊃');
LatexCmds.nsub = LatexCmds.notsub =
LatexCmds.nsubset = LatexCmds.notsubset =
bind(BinaryOperator,'\\not\\subset ','⊄');
LatexCmds.nsup = LatexCmds.notsup =
LatexCmds.nsupset = LatexCmds.notsupset =
LatexCmds.nsuperset = LatexCmds.notsuperset =
bind(BinaryOperator,'\\not\\supset ','⊅');
LatexCmds.sube = LatexCmds.subeq = LatexCmds.subsete = LatexCmds.subseteq =
bind(BinaryOperator,'\\subseteq ','⊆');
LatexCmds.supe = LatexCmds.supeq =
LatexCmds.supsete = LatexCmds.supseteq =
LatexCmds.supersete = LatexCmds.superseteq =
bind(BinaryOperator,'\\supseteq ','⊇');
LatexCmds.nsube = LatexCmds.nsubeq =
LatexCmds.notsube = LatexCmds.notsubeq =
LatexCmds.nsubsete = LatexCmds.nsubseteq =
LatexCmds.notsubsete = LatexCmds.notsubseteq =
bind(BinaryOperator,'\\not\\subseteq ','⊈');
LatexCmds.nsupe = LatexCmds.nsupeq =
LatexCmds.notsupe = LatexCmds.notsupeq =
LatexCmds.nsupsete = LatexCmds.nsupseteq =
LatexCmds.notsupsete = LatexCmds.notsupseteq =
LatexCmds.nsupersete = LatexCmds.nsuperseteq =
LatexCmds.notsupersete = LatexCmds.notsuperseteq =
bind(BinaryOperator,'\\not\\supseteq ','⊉');
//sum, product, coproduct, integral
var BigSymbol = P(Symbol, function(_, _super) {
_.init = function(ch, html) {
_super.init.call(this, ch, ''+html+'');
};
});
LatexCmds['∑'] = LatexCmds.sum = LatexCmds.summation = bind(BigSymbol,'\\sum ','∑');
LatexCmds['∏'] = LatexCmds.prod = LatexCmds.product = bind(BigSymbol,'\\prod ','∏');
LatexCmds.coprod = LatexCmds.coproduct = bind(BigSymbol,'\\coprod ','∐');
LatexCmds['∫'] = LatexCmds['int'] = LatexCmds.integral = bind(BigSymbol,'\\int ','∫');
//the canonical sets of numbers
LatexCmds.N = LatexCmds.naturals = LatexCmds.Naturals =
bind(VanillaSymbol,'\\mathbb{N}','ℕ');
LatexCmds.P =
LatexCmds.primes = LatexCmds.Primes =
LatexCmds.projective = LatexCmds.Projective =
LatexCmds.probability = LatexCmds.Probability =
bind(VanillaSymbol,'\\mathbb{P}','ℙ');
LatexCmds.Z = LatexCmds.integers = LatexCmds.Integers =
bind(VanillaSymbol,'\\mathbb{Z}','ℤ');
LatexCmds.Q = LatexCmds.rationals = LatexCmds.Rationals =
bind(VanillaSymbol,'\\mathbb{Q}','ℚ');
LatexCmds.R = LatexCmds.reals = LatexCmds.Reals =
bind(VanillaSymbol,'\\mathbb{R}','ℝ');
LatexCmds.C =
LatexCmds.complex = LatexCmds.Complex =
LatexCmds.complexes = LatexCmds.Complexes =
LatexCmds.complexplane = LatexCmds.Complexplane = LatexCmds.ComplexPlane =
bind(VanillaSymbol,'\\mathbb{C}','ℂ');
LatexCmds.H = LatexCmds.Hamiltonian = LatexCmds.quaternions = LatexCmds.Quaternions =
bind(VanillaSymbol,'\\mathbb{H}','ℍ');
//spacing
LatexCmds.quad = LatexCmds.emsp = bind(VanillaSymbol,'\\quad ',' ');
LatexCmds.qquad = bind(VanillaSymbol,'\\qquad ',' ');
/* spacing special characters, gonna have to implement this in LatexCommandInput::onText somehow
case ',':
return VanillaSymbol('\\, ',' ');
case ':':
return VanillaSymbol('\\: ',' ');
case ';':
return VanillaSymbol('\\; ',' ');
case '!':
return Symbol('\\! ','');
*/
//binary operators
LatexCmds.diamond = bind(VanillaSymbol, '\\diamond ', '◇');
LatexCmds.bigtriangleup = bind(VanillaSymbol, '\\bigtriangleup ', '△');
LatexCmds.ominus = bind(VanillaSymbol, '\\ominus ', '⊖');
LatexCmds.uplus = bind(VanillaSymbol, '\\uplus ', '⊎');
LatexCmds.bigtriangledown = bind(VanillaSymbol, '\\bigtriangledown ', '▽');
LatexCmds.sqcap = bind(VanillaSymbol, '\\sqcap ', '⊓');
LatexCmds.triangleleft = bind(VanillaSymbol, '\\triangleleft ', '⊲');
LatexCmds.sqcup = bind(VanillaSymbol, '\\sqcup ', '⊔');
LatexCmds.triangleright = bind(VanillaSymbol, '\\triangleright ', '⊳');
LatexCmds.odot = bind(VanillaSymbol, '\\odot ', '⊙');
LatexCmds.bigcirc = bind(VanillaSymbol, '\\bigcirc ', '◯');
LatexCmds.dagger = bind(VanillaSymbol, '\\dagger ', '');
LatexCmds.ddagger = bind(VanillaSymbol, '\\ddagger ', '');
LatexCmds.wr = bind(VanillaSymbol, '\\wr ', '≀');
LatexCmds.amalg = bind(VanillaSymbol, '\\amalg ', '∐');
//relationship symbols
LatexCmds.models = bind(VanillaSymbol, '\\models ', '⊨');
LatexCmds.prec = bind(VanillaSymbol, '\\prec ', '≺');
LatexCmds.succ = bind(VanillaSymbol, '\\succ ', '≻');
LatexCmds.preceq = bind(VanillaSymbol, '\\preceq ', '≼');
LatexCmds.succeq = bind(VanillaSymbol, '\\succeq ', '≽');
LatexCmds.simeq = bind(VanillaSymbol, '\\simeq ', '≃');
LatexCmds.mid = bind(VanillaSymbol, '\\mid ', '∣');
LatexCmds.ll = bind(VanillaSymbol, '\\ll ', '≪');
LatexCmds.gg = bind(VanillaSymbol, '\\gg ', '≫');
LatexCmds.parallel = bind(VanillaSymbol, '\\parallel ', '∥');
LatexCmds.bowtie = bind(VanillaSymbol, '\\bowtie ', '⋈');
LatexCmds.sqsubset = bind(VanillaSymbol, '\\sqsubset ', '⊏');
LatexCmds.sqsupset = bind(VanillaSymbol, '\\sqsupset ', '⊐');
LatexCmds.smile = bind(VanillaSymbol, '\\smile ', '⌣');
LatexCmds.sqsubseteq = bind(VanillaSymbol, '\\sqsubseteq ', '⊑');
LatexCmds.sqsupseteq = bind(VanillaSymbol, '\\sqsupseteq ', '⊒');
LatexCmds.doteq = bind(VanillaSymbol, '\\doteq ', '≐');
LatexCmds.frown = bind(VanillaSymbol, '\\frown ', '⌢');
LatexCmds.vdash = bind(VanillaSymbol, '\\vdash ', '⊦');
LatexCmds.dashv = bind(VanillaSymbol, '\\dashv ', '⊣');
//arrows
LatexCmds.longleftarrow = bind(VanillaSymbol, '\\longleftarrow ', '←');
LatexCmds.longrightarrow = bind(VanillaSymbol, '\\longrightarrow ', '→');
LatexCmds.Longleftarrow = bind(VanillaSymbol, '\\Longleftarrow ', '⇐');
LatexCmds.Longrightarrow = bind(VanillaSymbol, '\\Longrightarrow ', '⇒');
LatexCmds.longleftrightarrow = bind(VanillaSymbol, '\\longleftrightarrow ', '↔');
LatexCmds.updownarrow = bind(VanillaSymbol, '\\updownarrow ', '↕');
LatexCmds.Longleftrightarrow = bind(VanillaSymbol, '\\Longleftrightarrow ', '⇔');
LatexCmds.Updownarrow = bind(VanillaSymbol, '\\Updownarrow ', '⇕');
LatexCmds.mapsto = bind(VanillaSymbol, '\\mapsto ', '↦');
LatexCmds.nearrow = bind(VanillaSymbol, '\\nearrow ', '↗');
LatexCmds.hookleftarrow = bind(VanillaSymbol, '\\hookleftarrow ', '↩');
LatexCmds.hookrightarrow = bind(VanillaSymbol, '\\hookrightarrow ', '↪');
LatexCmds.searrow = bind(VanillaSymbol, '\\searrow ', '↘');
LatexCmds.leftharpoonup = bind(VanillaSymbol, '\\leftharpoonup ', '↼');
LatexCmds.rightharpoonup = bind(VanillaSymbol, '\\rightharpoonup ', '⇀');
LatexCmds.swarrow = bind(VanillaSymbol, '\\swarrow ', '↙');
LatexCmds.leftharpoondown = bind(VanillaSymbol, '\\leftharpoondown ', '↽');
LatexCmds.rightharpoondown = bind(VanillaSymbol, '\\rightharpoondown ', '⇁');
LatexCmds.nwarrow = bind(VanillaSymbol, '\\nwarrow ', '↖');
//Misc
LatexCmds.ldots = bind(VanillaSymbol, '\\ldots ', '…');
LatexCmds.cdots = bind(VanillaSymbol, '\\cdots ', '⋯');
LatexCmds.vdots = bind(VanillaSymbol, '\\vdots ', '⋮');
LatexCmds.ddots = bind(VanillaSymbol, '\\ddots ', '⋰');
LatexCmds.surd = bind(VanillaSymbol, '\\surd ', '√');
LatexCmds.triangle = bind(VanillaSymbol, '\\triangle ', '▵');
LatexCmds.ell = bind(VanillaSymbol, '\\ell ', 'ℓ');
LatexCmds.top = bind(VanillaSymbol, '\\top ', '⊤');
LatexCmds.flat = bind(VanillaSymbol, '\\flat ', '♭');
LatexCmds.natural = bind(VanillaSymbol, '\\natural ', '♮');
LatexCmds.sharp = bind(VanillaSymbol, '\\sharp ', '♯');
LatexCmds.wp = bind(VanillaSymbol, '\\wp ', '℘');
LatexCmds.bot = bind(VanillaSymbol, '\\bot ', '⊥');
LatexCmds.clubsuit = bind(VanillaSymbol, '\\clubsuit ', '♣');
LatexCmds.diamondsuit = bind(VanillaSymbol, '\\diamondsuit ', '♢');
LatexCmds.heartsuit = bind(VanillaSymbol, '\\heartsuit ', '♡');
LatexCmds.spadesuit = bind(VanillaSymbol, '\\spadesuit ', '♠');
//variable-sized
LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '∮');
LatexCmds.bigcap = bind(VanillaSymbol, '\\bigcap ', '∩');
LatexCmds.bigcup = bind(VanillaSymbol, '\\bigcup ', '∪');
LatexCmds.bigsqcup = bind(VanillaSymbol, '\\bigsqcup ', '⊔');
LatexCmds.bigvee = bind(VanillaSymbol, '\\bigvee ', '∨');
LatexCmds.bigwedge = bind(VanillaSymbol, '\\bigwedge ', '∧');
LatexCmds.bigodot = bind(VanillaSymbol, '\\bigodot ', '⊙');
LatexCmds.bigotimes = bind(VanillaSymbol, '\\bigotimes ', '⊗');
LatexCmds.bigoplus = bind(VanillaSymbol, '\\bigoplus ', '⊕');
LatexCmds.biguplus = bind(VanillaSymbol, '\\biguplus ', '⊎');
//delimiters
LatexCmds.lfloor = bind(VanillaSymbol, '\\lfloor ', '⌊');
LatexCmds.rfloor = bind(VanillaSymbol, '\\rfloor ', '⌋');
LatexCmds.lceil = bind(VanillaSymbol, '\\lceil ', '⌈');
LatexCmds.rceil = bind(VanillaSymbol, '\\rceil ', '⌉');
LatexCmds.slash = bind(VanillaSymbol, '\\slash ', '/');
LatexCmds.opencurlybrace = bind(VanillaSymbol, '\\opencurlybrace ', '{');
LatexCmds.closecurlybrace = bind(VanillaSymbol, '\\closecurlybrace ', '}');
//various symbols
LatexCmds.caret = bind(VanillaSymbol,'\\caret ','^');
LatexCmds.underscore = bind(VanillaSymbol,'\\underscore ','_');
LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\');
LatexCmds.vert = bind(VanillaSymbol,'|');
LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','⊥');
LatexCmds.nabla = LatexCmds.del = bind(VanillaSymbol,'\\nabla ','∇');
LatexCmds.hbar = bind(VanillaSymbol,'\\hbar ','ℏ');
LatexCmds.AA = LatexCmds.Angstrom = LatexCmds.angstrom =
bind(VanillaSymbol,'\\text\\AA ','Å');
LatexCmds.ring = LatexCmds.circ = LatexCmds.circle =
bind(VanillaSymbol,'\\circ ','∘');
LatexCmds.bull = LatexCmds.bullet = bind(VanillaSymbol,'\\bullet ','•');
LatexCmds.setminus = LatexCmds.smallsetminus =
bind(VanillaSymbol,'\\setminus ','∖');
LatexCmds.not = //bind(Symbol,'\\not ','/');
LatexCmds['¬'] = LatexCmds.neg = bind(VanillaSymbol,'\\neg ','¬');
LatexCmds['…'] = LatexCmds.dots = LatexCmds.ellip = LatexCmds.hellip =
LatexCmds.ellipsis = LatexCmds.hellipsis =
bind(VanillaSymbol,'\\dots ','…');
LatexCmds.converges =
LatexCmds.darr = LatexCmds.dnarr = LatexCmds.dnarrow = LatexCmds.downarrow =
bind(VanillaSymbol,'\\downarrow ','↓');
LatexCmds.dArr = LatexCmds.dnArr = LatexCmds.dnArrow = LatexCmds.Downarrow =
bind(VanillaSymbol,'\\Downarrow ','⇓');
LatexCmds.diverges = LatexCmds.uarr = LatexCmds.uparrow =
bind(VanillaSymbol,'\\uparrow ','↑');
LatexCmds.uArr = LatexCmds.Uparrow = bind(VanillaSymbol,'\\Uparrow ','⇑');
LatexCmds.to = bind(BinaryOperator,'\\to ','→');
LatexCmds.rarr = LatexCmds.rightarrow = bind(VanillaSymbol,'\\rightarrow ','→');
LatexCmds.implies = bind(BinaryOperator,'\\Rightarrow ','⇒');
LatexCmds.rArr = LatexCmds.Rightarrow = bind(VanillaSymbol,'\\Rightarrow ','⇒');
LatexCmds.gets = bind(BinaryOperator,'\\gets ','←');
LatexCmds.larr = LatexCmds.leftarrow = bind(VanillaSymbol,'\\leftarrow ','←');
LatexCmds.impliedby = bind(BinaryOperator,'\\Leftarrow ','⇐');
LatexCmds.lArr = LatexCmds.Leftarrow = bind(VanillaSymbol,'\\Leftarrow ','⇐');
LatexCmds.harr = LatexCmds.lrarr = LatexCmds.leftrightarrow =
bind(VanillaSymbol,'\\leftrightarrow ','↔');
LatexCmds.iff = bind(BinaryOperator,'\\Leftrightarrow ','⇔');
LatexCmds.hArr = LatexCmds.lrArr = LatexCmds.Leftrightarrow =
bind(VanillaSymbol,'\\Leftrightarrow ','⇔');
LatexCmds.Re = LatexCmds.Real = LatexCmds.real = bind(VanillaSymbol,'\\Re ','ℜ');
LatexCmds.Im = LatexCmds.imag =
LatexCmds.image = LatexCmds.imagin = LatexCmds.imaginary = LatexCmds.Imaginary =
bind(VanillaSymbol,'\\Im ','ℑ');
LatexCmds.part = LatexCmds.partial = bind(VanillaSymbol,'\\partial ','∂');
LatexCmds.inf = LatexCmds.infin = LatexCmds.infty = LatexCmds.infinity =
bind(VanillaSymbol,'\\infty ','∞');
LatexCmds.alef = LatexCmds.alefsym = LatexCmds.aleph = LatexCmds.alephsym =
bind(VanillaSymbol,'\\aleph ','ℵ');
LatexCmds.xist = //LOL
LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
bind(VanillaSymbol,'\\exists ','∃');
LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
bind(VanillaSymbol,'\\wedge ','∧');
LatexCmds.or = LatexCmds.lor = LatexCmds.vee = bind(VanillaSymbol,'\\vee ','∨');
LatexCmds.o = LatexCmds.O =
LatexCmds.empty = LatexCmds.emptyset =
LatexCmds.oslash = LatexCmds.Oslash =
LatexCmds.nothing = LatexCmds.varnothing =
bind(BinaryOperator,'\\varnothing ','∅');
LatexCmds.cup = LatexCmds.union = bind(BinaryOperator,'\\cup ','∪');
LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection =
bind(BinaryOperator,'\\cap ','∩');
LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'^\\circ ','°');
LatexCmds.ang = LatexCmds.angle = bind(VanillaSymbol,'\\angle ','∠');
var NonItalicizedFunction = P(Symbol, function(_, _super) {
_.init = function(fn) {
_super.init.call(this, '\\'+fn+' ', ''+fn+'');
};
_.respace = function()
{
this.jQ[0].className =
(this[R] instanceof SupSub || this[R] instanceof Bracket) ?
'' : 'non-italicized-function';
};
});
LatexCmds.ln =
LatexCmds.lg =
LatexCmds.log =
LatexCmds.span =
LatexCmds.proj =
LatexCmds.det =
LatexCmds.dim =
LatexCmds.min =
LatexCmds.max =
LatexCmds.mod =
LatexCmds.lcm =
LatexCmds.gcd =
LatexCmds.gcf =
LatexCmds.hcf =
LatexCmds.lim = NonItalicizedFunction;
(function() {
var trig = ['sin', 'cos', 'tan', 'sec', 'cosec', 'csc', 'cotan', 'cot'];
for (var i in trig) {
LatexCmds[trig[i]] =
LatexCmds[trig[i]+'h'] =
LatexCmds['a'+trig[i]] = LatexCmds['arc'+trig[i]] =
LatexCmds['a'+trig[i]+'h'] = LatexCmds['arc'+trig[i]+'h'] =
NonItalicizedFunction;
}
}());
/*************************************************
* Abstract classes of text blocks
************************************************/
/**
* Blocks of plain text, with one or two TextPiece's as children.
* Represents flat strings of typically serif-font Roman characters, as
* opposed to hierchical, nested, tree-structured math.
* Wraps a single HTMLSpanElement.
*/
var TextBlock = P(Node, function(_, _super) {
_.ctrlSeq = '\\text';
_.replaces = function(replacedText) {
if (replacedText instanceof Fragment)
this.replacedText = replacedText.remove().jQ.text();
else if (typeof replacedText === 'string')
this.replacedText = replacedText;
};
_.jQadd = function(jQ) {
_super.jQadd.call(this, jQ);
if (this.ends[L]) this.ends[L].jQadd(this.jQ[0].firstChild);
};
_.createBefore = function(cursor) {
var textBlock = this;
_super.createBefore.call(this, cursor);
if (textBlock[R].respace) textBlock[R].respace();
if (textBlock[L].respace) textBlock[L].respace();
textBlock.bubble('redraw');
cursor.insAtRightEnd(textBlock);
if (textBlock.replacedText)
for (var i = 0; i < textBlock.replacedText.length; i += 1)
textBlock.write(cursor, textBlock.replacedText.charAt(i));
};
_.parser = function() {
var textBlock = this;
// TODO: correctly parse text mode
var string = Parser.string;
var regex = Parser.regex;
var optWhitespace = Parser.optWhitespace;
return optWhitespace
.then(string('{')).then(regex(/^[^}]*/)).skip(string('}'))
.map(function(text) {
// TODO: is this the correct behavior when parsing
// the latex \text{} ? This violates the requirement that
// the text contents are always nonempty. Should we just
// disown the parent node instead?
TextPiece(text).adopt(textBlock, 0, 0);
return textBlock;
})
;
};
_.textContents = function() {
return this.foldChildren('', function(text, child) {
return text + child.text;
});
};
_.text = function() { return '"' + this.textContents() + '"'; };
_.latex = function() { return '\\text{' + this.textContents() + '}'; };
_.html = function() {
return (
''
+ this.textContents()
+ ''
);
};
_.onKey = function(key, e) {
if (key === 'Spacebar' || key === 'Shift-Spacebar') return false;
};
// editability methods: called by the cursor for editing, cursor movements,
// and selection of the MathQuill tree, these all take in a direction and
// the cursor
_.moveTowards = function(dir, cursor) { cursor.insAtDirEnd(-dir, this); };
_.moveOutOf = function(dir, cursor) { cursor.insDirOf(dir, this); };
_.unselectInto = _.moveTowards;
// TODO: make these methods part of a shared mixin or something.
_.selectTowards = MathCommand.prototype.selectTowards;
_.deleteTowards = MathCommand.prototype.deleteTowards;
_.selectChildren = MathBlock.prototype.selectChildren;
_.selectOutOf = function(dir, cursor) {
cursor.insDirOf(dir, this);
};
_.deleteOutOf = function(dir, cursor) {
// backspace and delete at ends of block don't unwrap
if (this.isEmpty()) cursor.insRightOf(this);
};
_.write = function(cursor, ch, replacedFragment) {
if (replacedFragment) replacedFragment.remove();
if (ch !== '$') {
if (!cursor[L]) TextPiece(ch).createBefore(cursor);
else cursor[L].appendText(ch);
}
else if (this.isEmpty()) {
cursor.insRightOf(this);
VanillaSymbol('\\$','$').createBefore(cursor);
}
else if (!cursor[R]) cursor.insRightOf(this);
else if (!cursor[L]) cursor.insLeftOf(this);
else { // split apart
var leftBlock = TextBlock();
var leftPc = this.ends[L];
leftPc.disown();
leftPc.adopt(leftBlock, 0, 0);
cursor.insLeftOf(this);
_super.createBefore.call(leftBlock, cursor);
}
return false;
};
_.seek = function(pageX, cursor) {
cursor.hide();
var textPc = fuseChildren(this);
// insert cursor at approx position in DOMTextNode
var avgChWidth = this.jQ.width()/this.text.length;
var approxPosition = Math.round((pageX - this.jQ.offset().left)/avgChWidth);
if (approxPosition <= 0) cursor.insAtLeftEnd(this);
else if (approxPosition >= textPc.text.length) cursor.insAtRightEnd(this);
else cursor.insLeftOf(textPc.splitRight(approxPosition));
// move towards mousedown (pageX)
var displ = pageX - cursor.show().offset().left; // displacement
var dir = displ && displ < 0 ? L : R;
var prevDispl = dir;
// displ * prevDispl > 0 iff displacement direction === previous direction
while (cursor[dir] && displ * prevDispl > 0) {
cursor[dir].moveTowards(dir, cursor);
prevDispl = displ;
displ = pageX - cursor.offset().left;
}
if (dir*displ < -dir*prevDispl) cursor[-dir].moveTowards(-dir, cursor);
if (!cursor.anticursor) {
// about to start mouse-selecting, the anticursor is gonna get put here
this.anticursorPosition = cursor[L] && cursor[L].text.length;
// ^ get it? 'cos if there's no cursor[L], it's 0... I'm a terrible person.
}
else if (cursor.anticursor.parent === this) {
// mouse-selecting within this TextBlock, re-insert the anticursor
var cursorPosition = cursor[L] && cursor[L].text.length;;
if (this.anticursorPosition === cursorPosition) {
cursor.anticursor = Point.copy(cursor);
}
else {
if (this.anticursorPosition < cursorPosition) {
var newTextPc = cursor[L].splitRight(this.anticursorPosition);
cursor[L] = newTextPc;
}
else {
var newTextPc = cursor[R].splitRight(this.anticursorPosition - cursorPosition);
}
cursor.anticursor = Point(this, newTextPc[L], newTextPc);
}
}
};
_.blur = function() {
MathBlock.prototype.blur.call(this);
fuseChildren(this);
};
function fuseChildren(self) {
self.jQ[0].normalize();
var textPcDom = self.jQ[0].firstChild;
var textPc = TextPiece(textPcDom.data);
textPc.jQadd(textPcDom);
self.children().disown();
return textPc.adopt(self, 0, 0);
}
_.focus = MathBlock.prototype.focus;
_.isEmpty = MathBlock.prototype.isEmpty;
});
/**
* Piece of plain text, with a TextBlock as a parent and no children.
* Wraps a single DOMTextNode.
* For convenience, has a .text property that's just a JavaScript string
* mirroring the text contents of the DOMTextNode.
* Text contents must always be nonempty.
*/
var TextPiece = P(Node, function(_, _super) {
_.init = function(text) {
_super.init.call(this);
this.text = text;
};
_.jQadd = function(dom) { this.dom = dom; this.jQ = $(dom); };
_.jQize = function() {
return this.jQadd(document.createTextNode(this.text));
};
_.appendText = function(text) {
this.text += text;
this.dom.appendData(text);
};
_.prependText = function(text) {
this.text = text + this.text;
this.dom.insertData(0, text);
};
_.insTextAtDirEnd = function(text, dir) {
prayDirection(dir);
if (dir === R) this.appendText(text);
else this.prependText(text);
};
_.splitRight = function(offset) {
var newPc = TextPiece(this.text.slice(offset)).adopt(this.parent, this, this[R]);
newPc.jQadd(this.dom.splitText(offset));
this.text = this.text.slice(0, offset);
return newPc;
};
function endChar(dir, text) {
return text.charAt(dir === L ? 0 : -1 + text.length);
}
_.moveTowards = function(dir, cursor) {
prayDirection(dir);
var ch = endChar(-dir, this.text)
var from = this[-dir];
if (from) from.insTextAtDirEnd(ch, dir);
else TextPiece(ch).createDir(-dir, cursor);
return this.deleteTowards(dir, cursor);
};
_.latex = function() { return this.text; };
_.deleteTowards = function(dir, cursor) {
if (this.text.length > 1) {
if (dir === R) {
this.dom.deleteData(0, 1);
this.text = this.text.slice(1);
}
else {
// note that the order of these 2 lines is annoyingly important
// (the second line mutates this.text.length)
this.dom.deleteData(-1 + this.text.length, 1);
this.text = this.text.slice(0, -1);
}
}
else {
this.remove();
this.jQ.remove();
cursor[dir] = this[dir];
}
};
_.selectTowards = function(dir, cursor) {
prayDirection(dir);
var anticursor = cursor.anticursor;
var ch = endChar(-dir, this.text)
if (!anticursor || anticursor[dir] === this) {
var newPc = TextPiece(ch).createDir(dir, cursor);
cursor.startSelection();
cursor.insDirOf(dir, newPc);
}
else {
var from = this[-dir];
if (from) from.insTextAtDirEnd(ch, dir);
else {
var newPc = TextPiece(ch).createDir(-dir, cursor);
newPc.jQ.insDirOf(-dir, cursor.selection.jQ);
}
if (this.text.length === 1 && anticursor[-dir] === this) {
anticursor[-dir] = this[-dir]; // `this` will be removed in deleteTowards
}
}
return this.deleteTowards(dir, cursor);
};
});
CharCmds.$ =
LatexCmds.text =
LatexCmds.textnormal =
LatexCmds.textrm =
LatexCmds.textup =
LatexCmds.textmd = TextBlock;
function makeTextBlock(latex, tagName, attrs) {
return P(TextBlock, {
ctrlSeq: latex,
htmlTemplate: '<'+tagName+' '+attrs+'>&0'+tagName+'>'
});
}
LatexCmds.em = LatexCmds.italic = LatexCmds.italics =
LatexCmds.emph = LatexCmds.textit = LatexCmds.textsl =
makeTextBlock('\\textit', 'i', 'class="text"');
LatexCmds.strong = LatexCmds.bold = LatexCmds.textbf =
makeTextBlock('\\textbf', 'b', 'class="text"');
LatexCmds.sf = LatexCmds.textsf =
makeTextBlock('\\textsf', 'span', 'class="sans-serif text"');
LatexCmds.tt = LatexCmds.texttt =
makeTextBlock('\\texttt', 'span', 'class="monospace text"');
LatexCmds.textsc =
makeTextBlock('\\textsc', 'span', 'style="font-variant:small-caps" class="text"');
LatexCmds.uppercase =
makeTextBlock('\\uppercase', 'span', 'style="text-transform:uppercase" class="text"');
LatexCmds.lowercase =
makeTextBlock('\\lowercase', 'span', 'style="text-transform:lowercase" class="text"');
// Parser MathCommand
var latexMathParser = (function() {
function commandToBlock(cmd) {
var block = MathBlock();
cmd.adopt(block, 0, 0);
return block;
}
function joinBlocks(blocks) {
var firstBlock = blocks[0] || MathBlock();
for (var i = 1; i < blocks.length; i += 1) {
blocks[i].children().adopt(firstBlock, firstBlock.ends[R], 0);
}
return firstBlock;
}
var string = Parser.string;
var regex = Parser.regex;
var letter = Parser.letter;
var any = Parser.any;
var optWhitespace = Parser.optWhitespace;
var succeed = Parser.succeed;
var fail = Parser.fail;
// Parsers yielding MathCommands
var variable = letter.map(Variable);
var symbol = regex(/^[^${}\\_^]/).map(VanillaSymbol);
var controlSequence =
regex(/^[^\\]/)
.or(string('\\').then(
regex(/^[a-z]+/i)
.or(regex(/^\s+/).result(' '))
.or(any)
)).then(function(ctrlSeq) {
var cmdKlass = LatexCmds[ctrlSeq];
if (cmdKlass) {
return cmdKlass(ctrlSeq).parser();
}
else {
return fail('unknown command: \\'+ctrlSeq);
}
})
;
var command =
controlSequence
.or(variable)
.or(symbol)
;
// Parsers yielding MathBlocks
var mathGroup = string('{').then(function() { return mathSequence; }).skip(string('}'));
var mathBlock = optWhitespace.then(mathGroup.or(command.map(commandToBlock)));
var mathSequence = mathBlock.many().map(joinBlocks).skip(optWhitespace);
var optMathBlock =
string('[').then(
mathBlock.then(function(block) {
return block.join('latex') !== ']' ? succeed(block) : fail();
})
.many().map(joinBlocks).skip(optWhitespace)
).skip(string(']'))
;
var latexMath = mathSequence;
latexMath.block = mathBlock;
latexMath.optBlock = optMathBlock;
return latexMath;
})();
/********************************************
* Cursor and Selection "singleton" classes
*******************************************/
/* The main thing that manipulates the Math DOM. Makes sure to manipulate the
HTML DOM to match. */
/* Sort of singletons, since there should only be one per editable math
textbox, but any one HTML document can contain many such textboxes, so any one
JS environment could actually contain many instances. */
//A fake cursor in the fake textbox that the math is rendered in.
var Cursor = P(Point, function(_) {
_.init = function(root) {
this.parent = this.root = root;
var jQ = this.jQ = this._jQ = $('');
//closured for setInterval
this.blink = function(){ jQ.toggleClass('blink'); };
this.upDownCache = {};
};
_.show = function() {
this.jQ = this._jQ.removeClass('blink');
if ('intervalId' in this) //already was shown, just restart interval
clearInterval(this.intervalId);
else { //was hidden and detached, insert this.jQ back into HTML DOM
if (this[R]) {
if (this.selection && this.selection.ends[L][L] === this[L])
this.jQ.insertBefore(this.selection.jQ);
else
this.jQ.insertBefore(this[R].jQ.first());
}
else
this.jQ.appendTo(this.parent.jQ);
this.parent.focus();
}
this.intervalId = setInterval(this.blink, 500);
return this;
};
_.hide = function() {
if ('intervalId' in this)
clearInterval(this.intervalId);
delete this.intervalId;
this.jQ.detach();
this.jQ = $();
return this;
};
_.withDirInsertAt = function(dir, parent, withDir, oppDir) {
if (parent !== this.parent) this.parent.blur();
this.parent = parent;
this[dir] = withDir;
this[-dir] = oppDir;
};
_.insDirOf = function(dir, el) {
prayDirection(dir);
this.withDirInsertAt(dir, el.parent, el[dir], el);
this.parent.jQ.addClass('hasCursor');
this.jQ.insDirOf(dir, el.jQ);
return this;
};
_.insLeftOf = function(el) { return this.insDirOf(L, el); };
_.insRightOf = function(el) { return this.insDirOf(R, el); };
_.insAtDirEnd = function(dir, el) {
prayDirection(dir);
this.withDirInsertAt(dir, el, 0, el.ends[dir]);
// never insert before textarea
if (dir === L && el.textarea) {
this.jQ.insDirOf(-dir, el.textarea);
}
else {
this.jQ.insAtDirEnd(dir, el.jQ);
}
el.focus();
return this;
};
_.insAtLeftEnd = function(el) { return this.insAtDirEnd(L, el); };
_.insAtRightEnd = function(el) { return this.insAtDirEnd(R, el); };
_.escapeDir = function(dir, key, e) {
prayDirection(dir);
// always prevent default of Spacebar, but only prevent default of Tab if
// not in the root editable
if (key === 'Spacebar' || this.parent !== this.root) {
e.preventDefault();
}
// want to be a noop if in the root editable (in fact, Tab has an unrelated
// default browser action if so)
if (this.parent === this.root) return;
clearUpDownCache(this);
this.endSelection();
this.show().clearSelection();
this.parent.moveOutOf(dir, this);
};
_.moveDirWithin = function(dir, block) {
prayDirection(dir);
if (this[dir]) this[dir].moveTowards(dir, this);
else if (this.parent !== block) this.parent.moveOutOf(dir, this);
};
_.moveLeftWithin = function(block) {
return this.moveDirWithin(L, block);
};
_.moveRightWithin = function(block) {
return this.moveDirWithin(R, block);
};
_.moveDir = function(dir) {
prayDirection(dir);
clearUpDownCache(this);
this.endSelection();
if (this.selection) {
this.insDirOf(dir, this.selection.ends[dir]).clearSelection();
}
else {
this.moveDirWithin(dir, this.root);
}
return this.show();
};
_.moveLeft = function() { return this.moveDir(L); };
_.moveRight = function() { return this.moveDir(R); };
/**
* moveUp and moveDown have almost identical algorithms:
* - first check left and right, if so insAtLeft/RightEnd of them
* - else check the parent's 'upOutOf'/'downOutOf' property:
* + if it's a function, call it with the cursor as the sole argument and
* use the return value as if it were the value of the property
* + if it's undefined, bubble up to the next ancestor.
* + if it's false, stop bubbling.
* + if it's a Node, jump up or down to it
*/
_.moveUp = function() { return moveUpDown(this, 'up'); };
_.moveDown = function() { return moveUpDown(this, 'down'); };
function moveUpDown(self, dir) {
var dirInto = dir+'Into', dirOutOf = dir+'OutOf';
if (self[R][dirInto]) self.insAtLeftEnd(self[R][dirInto]);
else if (self[L][dirInto]) self.insAtRightEnd(self[L][dirInto]);
else {
var ancestor = self.parent;
do {
var prop = ancestor[dirOutOf];
if (prop) {
if (typeof prop === 'function') prop = ancestor[dirOutOf](self);
if (prop === false) break;
if (prop instanceof Node) {
self.jumpUpDown(ancestor, prop);
break;
}
}
ancestor = ancestor.parent;
} while (ancestor !== self.root);
}
self.endSelection();
return self.clearSelection().show();
}
/**
* jump up or down from one block Node to another:
* - cache the current Point in the node we're jumping from
* - check if there's a Point in it cached for the node we're jumping to
* + if so put the cursor there,
* + if not seek a position in the node that is horizontally closest to
* the cursor's current position
*/
_.jumpUpDown = function(from, to) {
var self = this;
self.upDownCache[from.id] = Point.copy(self);
var cached = self.upDownCache[to.id];
if (cached) {
cached[R] ? self.insLeftOf(cached[R]) : self.insAtRightEnd(cached.parent);
}
else {
var pageX = self.offset().left;
to.seek(pageX, self);
}
};
_.seek = function(target, pageX, pageY) {
var cursor = this;
clearUpDownCache(cursor);
var nodeId = target.attr(mqBlockId) || target.attr(mqCmdId);
if (!nodeId) {
var targetParent = target.parent();
nodeId = targetParent.attr(mqBlockId) || targetParent.attr(mqCmdId);
}
var node = nodeId ? Node.byId[nodeId] : cursor.root;
pray('nodeId is the id of some Node that exists', node);
// don't clear selection until after getting node from target, in case
// target was selection span, otherwise target will have no parent and will
// seek from root, which is less accurate (e.g. fraction)
cursor.clearSelection().show();
node.seek(pageX, cursor);
return cursor;
};
_.offset = function() {
//in Opera 11.62, .getBoundingClientRect() and hence jQuery::offset()
//returns all 0's on inline elements with negative margin-right (like
//the cursor) at the end of their parent, so temporarily remove the
//negative margin-right when calling jQuery::offset()
//Opera bug DSK-360043
//http://bugs.jquery.com/ticket/11523
//https://github.com/jquery/jquery/pull/717
var self = this, offset = self.jQ.removeClass('cursor').offset();
self.jQ.addClass('cursor');
return offset;
}
_.writeLatex = function(latex) {
var self = this;
clearUpDownCache(self);
self.endSelection();
self.show().deleteSelection();
var all = Parser.all;
var eof = Parser.eof;
var block = latexMathParser.skip(eof).or(all.result(false)).parse(latex);
if (block) {
block.children().adopt(self.parent, self[L], self[R]);
block.jQize().insertBefore(self.jQ);
self[L] = block.ends[R];
block.finalizeInsert();
self.parent.bubble('redraw');
}
return this.hide();
};
_.write = function(ch) {
var seln = this.prepareWrite();
return this.insertCh(ch, seln);
};
_.insertCh = function(ch, replacedFragment) {
this.parent.write(this, ch, replacedFragment);
return this;
};
_.insertCmd = function(latexCmd, replacedFragment) {
var cmd = LatexCmds[latexCmd];
if (cmd) {
cmd = cmd(latexCmd);
if (replacedFragment) cmd.replaces(replacedFragment);
cmd.createBefore(this);
}
else {
cmd = TextBlock();
cmd.replaces(latexCmd);
cmd.ends[L].focus = function(){ delete this.focus; return this; };
cmd.createBefore(this);
this.insRightOf(cmd);
if (replacedFragment)
replacedFragment.remove();
}
return this;
};
_.unwrapGramp = function() {
var gramp = this.parent.parent;
var greatgramp = gramp.parent;
var rightward = gramp[R];
var cursor = this;
var leftward = gramp[L];
gramp.disown().eachChild(function(uncle) {
if (uncle.isEmpty()) return;
uncle.children()
.adopt(greatgramp, leftward, rightward)
.each(function(cousin) {
cousin.jQ.insertBefore(gramp.jQ.first());
})
;
leftward = uncle.ends[R];
});
if (!this[R]) { //then find something to be rightward to insLeftOf
if (this[L])
this[R] = this[L][R];
else {
while (!this[R]) {
this.parent = this.parent[R];
if (this.parent)
this[R] = this.parent.ends[L];
else {
this[R] = gramp[R];
this.parent = greatgramp;
break;
}
}
}
}
if (this[R])
this.insLeftOf(this[R]);
else
this.insAtRightEnd(greatgramp);
gramp.jQ.remove();
if (gramp[L])
gramp[L].respace();
if (gramp[R])
gramp[R].respace();
};
_.deleteDir = function(dir) {
prayDirection(dir);
clearUpDownCache(this);
this.endSelection();
this.show();
if (this.deleteSelection()); // pass
else if (this[dir]) this[dir].deleteTowards(dir, this);
else if (this.parent !== this.root) this.parent.deleteOutOf(dir, this);
if (this[L])
this[L].respace();
if (this[R])
this[R].respace();
this.parent.bubble('redraw');
return this;
};
_.backspace = function() { return this.deleteDir(L); };
_.deleteForward = function() { return this.deleteDir(R); };
_.select = function() {
var anticursor = this.anticursor;
if (this[L] === anticursor[L] && this.parent === anticursor.parent) return false;
// `this` cursor and the anticursor should be in the same tree, because
// the mousemove handler attached to the document, unlike the one attached
// to the root HTML DOM element, doesn't try to get the math tree node of
// the mousemove target, and Cursor::seek() based solely on coordinates
// stays within the tree of `this` cursor's root.
var selection = Fragment.between(this, anticursor);
var leftEnd = selection.ends[L];
var rightEnd = selection.ends[R];
var lca = leftEnd.parent;
lca.selectChildren(this.hide(), leftEnd, rightEnd);
this.root.selectionChanged();
return true;
};
_.selectDir = function(dir) {
var cursor = this, seln = cursor.selection;
prayDirection(dir);
clearUpDownCache(cursor);
if (!cursor.anticursor) cursor.startSelection();
var node = cursor[dir];
if (node) {
// "if node we're selecting towards is inside selection (hence retracting)
// and is on the *far side* of the selection (hence is only node selected)
// and the anticursor is *inside* that node, not just on the other side"
if (seln && seln.ends[dir] === node && cursor.anticursor[-dir] !== node) {
node.unselectInto(dir, cursor);
}
else node.selectTowards(dir, cursor);
}
else if (cursor.parent !== cursor.root) {
cursor.parent.selectOutOf(dir, cursor);
}
cursor.clearSelection();
cursor.select() || cursor.show();
};
_.selectLeft = function() { return this.selectDir(L); };
_.selectRight = function() { return this.selectDir(R); };
_.startSelection = function() {
this.anticursor = Point.copy(this);
};
_.endSelection = function() {
delete this.anticursor;
};
function clearUpDownCache(self) {
self.upDownCache = {};
}
_.prepareMove = function() {
clearUpDownCache(this);
this.endSelection();
return this.show().clearSelection();
};
_.prepareEdit = function() {
clearUpDownCache(this);
this.endSelection();
return this.show().deleteSelection();
};
_.prepareWrite = function() {
clearUpDownCache(this);
this.endSelection();
return this.show().replaceSelection();
};
_.clearSelection = function() {
if (this.selection) {
this.selection.clear();
delete this.selection;
this.root.selectionChanged();
}
return this;
};
_.deleteSelection = function() {
if (!this.selection) return false;
this[L] = this.selection.ends[L][L];
this[R] = this.selection.ends[R][R];
this.selection.remove();
this.root.selectionChanged();
return delete this.selection;
};
_.replaceSelection = function() {
var seln = this.selection;
if (seln) {
this[L] = seln.ends[L][L];
this[R] = seln.ends[R][R];
delete this.selection;
}
return seln;
};
});
var Selection = P(Fragment, function(_, _super) {
_.init = function(leftEnd, rightEnd) {
var seln = this;
// just select one thing if only one argument
_super.init.call(seln, leftEnd, rightEnd || leftEnd);
seln.jQwrap(seln.jQ);
};
_.jQwrap = function(children) {
this.jQ = children.wrapAll('').parent();
//can't do wrapAll(this.jQ = $(...)) because wrapAll will clone it
};
_.adopt = function() {
this.jQ.replaceWith(this.jQ = this.jQ.children());
return _super.adopt.apply(this, arguments);
};
_.clear = function() {
// using the browser's native .childNodes property so that we
// don't discard text nodes.
this.jQ.replaceWith(this.jQ[0].childNodes);
return this;
};
});
/*********************************************************
* The actual jQuery plugin and document ready handlers.
********************************************************/
//The publicy exposed method of jQuery.prototype, available (and meant to be
//called) on jQuery-wrapped HTML DOM elements.
jQuery.fn.mathquill = function(cmd, latex) {
switch (cmd) {
case 'redraw':
return this.each(function() {
var blockId = $(this).attr(mqBlockId),
rootBlock = blockId && Node.byId[blockId];
if (rootBlock) {
(function postOrderRedraw(el) {
el.eachChild(postOrderRedraw);
if (el.redraw) el.redraw();
}(rootBlock));
}
});
case 'revert':
return this.each(function() {
var blockId = $(this).attr(mqBlockId),
block = blockId && Node.byId[blockId];
if (block && block.revert)
block.revert();
});
case 'latex':
if (arguments.length > 1) {
return this.each(function() {
var blockId = $(this).attr(mqBlockId),
block = blockId && Node.byId[blockId];
if (block)
block.renderLatex(latex);
});
}
var blockId = $(this).attr(mqBlockId),
block = blockId && Node.byId[blockId];
return block && block.latex();
case 'text':
var blockId = $(this).attr(mqBlockId),
block = blockId && Node.byId[blockId];
return block && block.text();
case 'html':
return this.html().replace(/ ?hasCursor|hasCursor /, '')
.replace(/ class=(""|(?= |>))/g, '')
.replace(/<\/span>/i, '')
.replace(/