blob: 486dde425ba2785d6080d6903e15c33d0d99ca2f [file] [log] [blame]
/*********************************************************
* The publicly exposed MathQuill API.
********************************************************/
/**
* Global function that takes an HTML element and, if it's the root HTML element
* of a static math or math or text field, returns its API object (if not, null).
* Identity of API object guaranteed if called multiple times, i.e.:
*
* var mathfield = MathQuill.MathField(mathFieldSpan);
* assert(MathQuill(mathFieldSpan) === mathfield);
* assert(MathQuill(mathFieldSpan) === MathQuill(mathFieldSpan));
*
*/
function MathQuill(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);
return blockId ? Node.byId[blockId].controller.API : null;
};
MathQuill.noConflict = function() {
window.MathQuill = origMathQuill;
return MathQuill;
};
/**
* Returns function (to be publicly exported) that MathQuill-ifies an HTML
* element and returns an API object. If the element had already been MathQuill-
* ified into the same kind, return the original API object (if different kind
* or not an HTML element, null).
*/
function APIFnFor(APIClass) {
function APIFn(el, opts) {
var mq = MathQuill(el);
if (mq instanceof APIClass || !el || !el.nodeType) return mq;
return APIClass($(el), opts);
}
APIFn.prototype = APIClass.prototype;
return APIFn;
}
var Options = P(), optionProcessors = {};
MathQuill.__options = Options.p;
var AbstractMathQuill = P(function(_) {
_.init = function() { throw "wtf don't call me, I'm 'abstract'"; };
_.initRoot = function(root, el, opts) {
this.__options = Options();
this.config(opts);
var ctrlr = Controller(this, root, el);
ctrlr.createTextarea();
var contents = el.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 =
MathQuill.config = function(opts) {
for (var opt in opts) if (opts.hasOwnProperty(opt)) {
var optVal = opts[opt], processor = optionProcessors[opt];
this.__options[opt] = (processor ? processor(optVal) : optVal);
}
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;
};
});
MathQuill.prototype = AbstractMathQuill.prototype;
MathQuill.StaticMath = APIFnFor(P(AbstractMathQuill, function(_, super_) {
_.init = function(el) {
this.initRoot(MathBlock(), el.addClass('mq-math-mode'));
this.__controller.delegateMouseEvents();
this.__controller.staticMathTextareaEvents();
};
_.latex = function() {
var returned = super_.latex.apply(this, arguments);
if (arguments.length > 0) {
this.__controller.root.postOrder('registerInnerField', this.innerFields = []);
}
return returned;
};
}));
var EditableField = MathQuill.EditableField = P(AbstractMathQuill, function(_) {
_.initRootAndEvents = function(root, el, opts) {
this.initRoot(root, el, opts);
this.__controller.editable = true;
this.__controller.delegateMouseEvents();
this.__controller.editablesTextareaEvents();
};
_.focus = function() { this.__controller.textarea.focus(); return this; };
_.blur = function() { this.__controller.textarea.blur(); return this; };
_.write = function(latex) {
this.__controller.writeLatex(latex);
if (this.__controller.blurred) this.__controller.cursor.hide().parent.blur();
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());
}
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;
};
});
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');
};
}
/**
* Interface Versioning (#459) to allow us to virtually guarantee backcompat.
* v0.10.x introduces it, so for now, don't completely break the API before
* MathQuill.interfaceVersion(1) is called, just complain with console.warn().
*
* .noConflict() is shimmed here directly because it needs to be modified,
* the rest are shimmed in outro.js so that MathQuill.MathField.prototype etc
* can be accessed (same reason this is at the end of publicapi.js, so that
* MathQuill.prototype can be accessed).
*/
function insistOnInterVer() {
if (window.console) console.warn(
'Please call MathQuill.interfaceVersion(1) before doing anything else ' +
'with the MathQuill API. This will be required starting v1.0.0.'
);
}
function preInterVerMathQuill(el) {
insistOnInterVer();
return MathQuill(el);
};
preInterVerMathQuill.prototype = MathQuill.prototype;
preInterVerMathQuill.interfaceVersion = function(v) {
if (v !== 1) throw 'Only interface version 1 supported. You specified: ' + v;
return window.MathQuill = MathQuill;
};
preInterVerMathQuill.noConflict = function() {
insistOnInterVer();
window.MathQuill = origMathQuill;
return preInterVerMathQuill;
};
var origMathQuill = window.MathQuill;
window.MathQuill = preInterVerMathQuill;