| /********************************************************* |
| * The publicly exposed MathQuill API. |
| ********************************************************/ |
| |
| var API = {}, Options = P(), optionProcessors = {}, Progenote = P(), EMBEDS = {}; |
| |
| /** |
| * Interface Versioning (#459, #495) to allow us to virtually guarantee |
| * backcompat. v0.10.x introduces it, so for now, don't completely break the |
| * API for people who don't know about it, just complain with console.warn(). |
| * |
| * The methods are shimmed in outro.js so that MQ.MathField.prototype etc can |
| * be accessed. |
| */ |
| function insistOnInterVer() { |
| if (window.console) console.warn( |
| 'You are using the MathQuill API without specifying an interface version, ' + |
| 'which will fail in v1.0.0. Easiest fix is to do the following before ' + |
| 'doing anything else:\n' + |
| '\n' + |
| ' MathQuill = MathQuill.getInterface(1);\n' + |
| ' // now MathQuill.MathField() works like it used to\n' + |
| '\n' + |
| 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' + |
| ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide' |
| ); |
| } |
| // globally exported API object |
| function MathQuill(el) { |
| insistOnInterVer(); |
| return MQ1(el); |
| }; |
| MathQuill.prototype = Progenote.p; |
| MathQuill.VERSION = "{VERSION}"; |
| MathQuill.interfaceVersion = function(v) { |
| // shim for #459-era interface versioning (ended with #495) |
| if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v; |
| insistOnInterVer = function() { |
| if (window.console) console.warn( |
| 'You called MathQuill.interfaceVersion(1); to specify the interface ' + |
| 'version, which will fail in v1.0.0. You can fix this easily by doing ' + |
| 'this before doing anything else:\n' + |
| '\n' + |
| ' MathQuill = MathQuill.getInterface(1);\n' + |
| ' // now MathQuill.MathField() works like it used to\n' + |
| '\n' + |
| 'See also the "`dev` branch (2014–2015) → v0.10.0 Migration Guide" at\n' + |
| ' https://github.com/mathquill/mathquill/wiki/%60dev%60-branch-(2014%E2%80%932015)-%E2%86%92-v0.10.0-Migration-Guide' |
| ); |
| }; |
| insistOnInterVer(); |
| return MathQuill; |
| }; |
| MathQuill.getInterface = getInterface; |
| |
| var MIN = getInterface.MIN = 1, MAX = getInterface.MAX = 2; |
| function getInterface(v) { |
| if (!(MIN <= v && v <= MAX)) throw 'Only interface versions between ' + |
| MIN + ' and ' + MAX + ' supported. You specified: ' + v; |
| |
| /** |
| * Function that takes an HTML element and, if it's the root HTML element of a |
| * static math or math or text field, returns an API object for it (else, null). |
| * |
| * var mathfield = MQ.MathField(mathFieldSpan); |
| * assert(MQ(mathFieldSpan).id === mathfield.id); |
| * assert(MQ(mathFieldSpan).id === MQ(mathFieldSpan).id); |
| * |
| */ |
| function MQ(el) { |
| if (!el || !el.nodeType) return null; // check that `el` is a HTML element, using the |
| // same technique as jQuery: https://github.com/jquery/jquery/blob/679536ee4b7a92ae64a5f58d90e9cc38c001e807/src/core/init.js#L92 |
| var blockId = $(el).children('.mq-root-block').attr(mqBlockId); |
| var ctrlr = blockId && Node.byId[blockId].controller; |
| return ctrlr ? APIClasses[ctrlr.KIND_OF_MQ](ctrlr) : null; |
| }; |
| var APIClasses = {}; |
| |
| MQ.L = L; |
| MQ.R = R; |
| MQ.saneKeyboardEvents = saneKeyboardEvents; |
| |
| function config(currentOptions, newOptions) { |
| if (newOptions && newOptions.handlers) { |
| newOptions.handlers = { fns: newOptions.handlers, APIClasses: APIClasses }; |
| } |
| for (var name in newOptions) if (newOptions.hasOwnProperty(name)) { |
| var value = newOptions[name], processor = optionProcessors[name]; |
| currentOptions[name] = (processor ? processor(value) : value); |
| } |
| } |
| MQ.config = function(opts) { config(Options.p, opts); return this; }; |
| MQ.registerEmbed = function(name, options) { |
| if (!/^[a-z][a-z0-9]*$/i.test(name)) { |
| throw 'Embed name must start with letter and be only letters and digits'; |
| } |
| EMBEDS[name] = options; |
| }; |
| |
| var AbstractMathQuill = APIClasses.AbstractMathQuill = P(Progenote, function(_) { |
| _.init = function(ctrlr) { |
| this.__controller = ctrlr; |
| this.__options = ctrlr.options; |
| this.id = ctrlr.id; |
| this.data = ctrlr.data; |
| }; |
| _.__mathquillify = function(classNames) { |
| var ctrlr = this.__controller, root = ctrlr.root, el = ctrlr.container; |
| ctrlr.createTextarea(); |
| |
| var contents = el.addClass(classNames).contents().detach(); |
| root.jQ = |
| $('<span class="mq-root-block"/>').attr(mqBlockId, root.id).appendTo(el); |
| this.latex(contents.text()); |
| |
| this.revert = function() { |
| return el.empty().unbind('.mathquill') |
| .removeClass('mq-editable-field mq-math-mode mq-text-mode') |
| .append(contents); |
| }; |
| }; |
| _.config = function(opts) { config(this.__options, opts); return this; }; |
| _.el = function() { return this.__controller.container[0]; }; |
| _.text = function() { return this.__controller.exportText(); }; |
| _.latex = function(latex) { |
| if (arguments.length > 0) { |
| this.__controller.renderLatexMath(latex); |
| if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur(); |
| return this; |
| } |
| return this.__controller.exportLatex(); |
| }; |
| _.html = function() { |
| return this.__controller.root.jQ.html() |
| .replace(/ mathquill-(?:command|block)-id="?\d+"?/g, '') |
| .replace(/<span class="?mq-cursor( mq-blink)?"?>.?<\/span>/i, '') |
| .replace(/ mq-hasCursor|mq-hasCursor ?/, '') |
| .replace(/ class=(""|(?= |>))/g, ''); |
| }; |
| _.reflow = function() { |
| this.__controller.root.postOrder('reflow'); |
| return this; |
| }; |
| }); |
| MQ.prototype = AbstractMathQuill.prototype; |
| |
| APIClasses.EditableField = P(AbstractMathQuill, function(_, super_) { |
| _.__mathquillify = function() { |
| super_.__mathquillify.apply(this, arguments); |
| this.__controller.editable = true; |
| this.__controller.delegateMouseEvents(); |
| this.__controller.editablesTextareaEvents(); |
| return this; |
| }; |
| _.focus = function() { this.__controller.textarea.focus(); return this; }; |
| _.blur = function() { this.__controller.textarea.blur(); return this; }; |
| _.write = function(latex) { |
| this.__controller.writeLatex(latex); |
| this.__controller.scrollHoriz(); |
| if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur(); |
| return this; |
| }; |
| _.empty = function() { |
| var root = this.__controller.root, cursor = this.__controller.cursor; |
| root.eachChild('postOrder', 'dispose'); |
| root.ends[L] = root.ends[R] = 0; |
| root.jQ.empty(); |
| delete cursor.selection; |
| cursor.insAtRightEnd(root); |
| return this; |
| }; |
| _.cmd = function(cmd) { |
| var ctrlr = this.__controller.notify(), cursor = ctrlr.cursor; |
| if (/^\\[a-z]+$/i.test(cmd)) { |
| cmd = cmd.slice(1); |
| var klass = LatexCmds[cmd]; |
| if (klass) { |
| cmd = klass(cmd); |
| if (cursor.selection) cmd.replaces(cursor.replaceSelection()); |
| cmd.createLeftOf(cursor.show()); |
| this.__controller.scrollHoriz(); |
| } |
| else /* TODO: API needs better error reporting */; |
| } |
| else cursor.parent.write(cursor, cmd); |
| if (ctrlr.blurred) cursor.hide().parent.blur(); |
| return this; |
| }; |
| _.select = function() { |
| var ctrlr = this.__controller; |
| ctrlr.notify('move').cursor.insAtRightEnd(ctrlr.root); |
| while (ctrlr.cursor[L]) ctrlr.selectLeft(); |
| return this; |
| }; |
| _.clearSelection = function() { |
| this.__controller.cursor.clearSelection(); |
| return this; |
| }; |
| |
| _.moveToDirEnd = function(dir) { |
| this.__controller.notify('move').cursor.insAtDirEnd(dir, this.__controller.root); |
| return this; |
| }; |
| _.moveToLeftEnd = function() { return this.moveToDirEnd(L); }; |
| _.moveToRightEnd = function() { return this.moveToDirEnd(R); }; |
| |
| _.keystroke = function(keys) { |
| var keys = keys.replace(/^\s+|\s+$/g, '').split(/\s+/); |
| for (var i = 0; i < keys.length; i += 1) { |
| this.__controller.keystroke(keys[i], { preventDefault: noop }); |
| } |
| return this; |
| }; |
| _.typedText = function(text) { |
| for (var i = 0; i < text.length; i += 1) this.__controller.typedText(text.charAt(i)); |
| return this; |
| }; |
| _.dropEmbedded = function(pageX, pageY, options) { |
| var clientX = pageX - $(window).scrollLeft(); |
| var clientY = pageY - $(window).scrollTop(); |
| |
| var el = document.elementFromPoint(clientX, clientY); |
| this.__controller.seek($(el), pageX, pageY); |
| var cmd = Embed().setOptions(options); |
| cmd.createLeftOf(this.__controller.cursor); |
| }; |
| _.clickAt = function(clientX, clientY, target) { |
| target = target || document.elementFromPoint(clientX, clientY); |
| |
| var ctrlr = this.__controller, root = ctrlr.root; |
| if (!jQuery.contains(root.jQ[0], target)) target = root.jQ[0]; |
| ctrlr.seek($(target), clientX + pageXOffset, clientY + pageYOffset); |
| if (ctrlr.blurred) this.focus(); |
| return this; |
| }; |
| _.ignoreNextMousedown = function(fn) { |
| this.__controller.cursor.options.ignoreNextMousedown = fn; |
| return this; |
| }; |
| }); |
| MQ.EditableField = function() { throw "wtf don't call me, I'm 'abstract'"; }; |
| MQ.EditableField.prototype = APIClasses.EditableField.prototype; |
| |
| /** |
| * Export the API functions that MathQuill-ify an HTML element into API objects |
| * of each class. If the element had already been MathQuill-ified but into a |
| * different kind (or it's not an HTML element), return null. |
| */ |
| for (var kind in API) (function(kind, defAPIClass) { |
| var APIClass = APIClasses[kind] = defAPIClass(APIClasses); |
| MQ[kind] = function(el, opts) { |
| var mq = MQ(el); |
| if (mq instanceof APIClass || !el || !el.nodeType) return mq; |
| var ctrlr = Controller(APIClass.RootBlock(), $(el), Options()); |
| ctrlr.KIND_OF_MQ = kind; |
| return APIClass(ctrlr).__mathquillify(opts, v); |
| }; |
| MQ[kind].prototype = APIClass.prototype; |
| }(kind, API[kind])); |
| |
| return MQ; |
| } |
| |
| MathQuill.noConflict = function() { |
| window.MathQuill = origMathQuill; |
| return MathQuill; |
| }; |
| var origMathQuill = window.MathQuill; |
| window.MathQuill = MathQuill; |
| |
| function RootBlockMixin(_) { |
| var names = 'moveOutOf deleteOutOf selectOutOf upOutOf downOutOf'.split(' '); |
| for (var i = 0; i < names.length; i += 1) (function(name) { |
| _[name] = function(dir) { this.controller.handle(name, dir); }; |
| }(names[i])); |
| _.reflow = function() { |
| this.controller.handle('reflow'); |
| this.controller.handle('edited'); |
| this.controller.handle('edit'); |
| }; |
| } |