Merge pull request #623 from mathquill/docs.readthedocs
Refactor documentation from README into Read the Docs
diff --git a/Makefile b/Makefile
index dcb2497..8acc2a6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,23 @@
#
+# -*- Prerequisites -*-
+#
+
+# the fact that 'I am Node.js' is unquoted here looks wrong to me but it
+# CAN'T be quoted, I tried. Apparently in GNU Makefiles, in the paren+comma
+# syntax for conditionals, quotes are literal; and because the $(shell...)
+# call has parentheses and single and double quotes, the quoted syntaxes
+# don't work (I tried), we HAVE to use the paren+comma syntax
+ifneq ($(shell node -e 'console.log("I am Node.js")'), I am Node.js)
+ ifeq ($(shell nodejs -e 'console.log("I am Node.js")' 2>/dev/null), I am Node.js)
+ $(error You have /usr/bin/nodejs but no /usr/bin/node, please 'sudo apt-get install nodejs-legacy' (see http://stackoverflow.com/a/21171188/362030 ))
+ endif
+
+ $(error Please install Node.js: https://nodejs.org/ )
+endif
+
+
+
+#
# -*- Configuration -*-
#
diff --git a/package.json b/package.json
index e9889de..9c8fb28 100644
--- a/package.json
+++ b/package.json
@@ -8,10 +8,10 @@
"url": "https://github.com/mathquill/mathquill.git"
},
"dependencies": {
- "pjs": "3.x"
+ "pjs": ">=3.1.0 <5.0.0"
},
"devDependencies": {
- "mocha": "*",
+ "mocha": ">=2.4.1",
"uglify-js": "2.x",
"less": ">=1.5.1"
}
diff --git a/src/commands/math/commands.js b/src/commands/math/commands.js
index 414edaa..1538c51 100644
--- a/src/commands/math/commands.js
+++ b/src/commands/math/commands.js
@@ -138,7 +138,7 @@
var SupSub = P(MathCommand, function(_, super_) {
_.ctrlSeq = '_{...}^{...}';
_.createLeftOf = function(cursor) {
- if (!cursor[L] && cursor.options.supSubsRequireOperand) return;
+ if (!this.replacedFragment && !cursor[L] && cursor.options.supSubsRequireOperand) return;
return super_.createLeftOf.apply(this, arguments);
};
_.contactWeld = function(cursor) {
@@ -181,7 +181,6 @@
break;
}
}
- this.respace();
};
Options.p.charsThatBreakOutOfSupSub = '';
_.finalizeTree = function() {
@@ -230,10 +229,6 @@
}
return latex('_', this.sub) + latex('^', this.sup);
};
- _.respace = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
- if (dir === R) return; // ignore if sibling only changed on the right
- this.jQ.toggleClass('mq-limit', this[L].ctrlSeq === '\\int ');
- };
_.addBlock = function(block) {
if (this.supsub === 'sub') {
this.sup = this.upInto = this.sub.upOutOf = block;
@@ -382,7 +377,23 @@
LatexCmds['∫'] =
LatexCmds['int'] =
-LatexCmds.integral = bind(Symbol,'\\int ','<big>∫</big>');
+LatexCmds.integral = P(SummationNotation, function(_, super_) {
+ _.init = function() {
+ var htmlTemplate =
+ '<span class="mq-int mq-non-leaf">'
+ + '<big>∫</big>'
+ + '<span class="mq-supsub mq-non-leaf">'
+ + '<span class="mq-sup"><span class="mq-sup-inner">&1</span></span>'
+ + '<span class="mq-sub">&0</span>'
+ + '<span style="display:inline-block;width:0">​</span>'
+ + '</span>'
+ + '</span>'
+ ;
+ Symbol.prototype.init.call(this, '\\int ', htmlTemplate);
+ };
+ // FIXME: refactor rather than overriding
+ _.createLeftOf = MathCommand.p.createLeftOf;
+});
var Fraction =
LatexCmds.frac =
diff --git a/src/commands/text.js b/src/commands/text.js
index d4352bd..59614ee 100644
--- a/src/commands/text.js
+++ b/src/commands/text.js
@@ -296,7 +296,6 @@
};
});
-CharCmds.$ =
LatexCmds.text =
LatexCmds.textnormal =
LatexCmds.textrm =
diff --git a/src/css/math.less b/src/css/math.less
index fd828b0..aae409d 100644
--- a/src/css/math.less
+++ b/src/css/math.less
@@ -90,6 +90,28 @@
font-size: 200%;
}
+ .mq-int {
+ > big {
+ display: inline-block;
+ .transform(scaleX(.7));
+ vertical-align: -.16em;
+ }
+
+ > .mq-supsub {
+ font-size: 80%;
+ vertical-align: -1.1em;
+ padding-right: .2em;
+
+ > .mq-sup > .mq-sup-inner {
+ vertical-align: 1.3em;
+ }
+
+ > .mq-sub {
+ margin-left: -.35em;
+ }
+ }
+ }
+
.mq-roman {
font-style: normal;
}
@@ -132,10 +154,6 @@
text-align: left;
font-size: 90%;
vertical-align: -.5em;
- &.mq-limit {
- font-size: 80%;
- vertical-align: -.4em;
- }
&.mq-sup-only {
vertical-align: .5em;
@@ -154,9 +172,6 @@
display: block;
float: left;
}
- &.mq-limit .mq-sub {
- margin-left: -.25em;
- }
.mq-binary-operator {
padding: 0 .1em;
diff --git a/src/publicapi.js b/src/publicapi.js
index 022dfda..2ccdeb2 100644
--- a/src/publicapi.js
+++ b/src/publicapi.js
@@ -213,6 +213,19 @@
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;
diff --git a/src/services/mouse.js b/src/services/mouse.js
index c060461..b2b543a 100644
--- a/src/services/mouse.js
+++ b/src/services/mouse.js
@@ -3,6 +3,7 @@
*******************************************************/
Controller.open(function(_) {
+ Options.p.ignoreNextMousedown = noop;
_.delegateMouseEvents = function() {
var ultimateRootjQ = this.root.jQ;
//drag-to-select event handling
@@ -12,6 +13,12 @@
var ctrlr = root.controller, cursor = ctrlr.cursor, blink = cursor.blink;
var textareaSpan = ctrlr.textareaSpan, textarea = ctrlr.textarea;
+ e.preventDefault(); // doesn't work in IE≤8, but it's a one-line fix:
+ e.target.unselectable = true; // http://jsbin.com/yagekiji/1
+
+ if (cursor.options.ignoreNextMousedown(e)) return;
+ else cursor.options.ignoreNextMousedown = noop;
+
var target;
function mousemove(e) { target = $(e.target); }
function docmousemove(e) {
@@ -42,8 +49,6 @@
if (!ctrlr.editable) rootjQ.prepend(textareaSpan);
textarea.focus();
}
- e.preventDefault(); // doesn't work in IE≤8, but it's a one-line fix:
- e.target.unselectable = true; // http://jsbin.com/yagekiji/1
cursor.blink = noop;
ctrlr.seek($(e.target), e.pageX, e.pageY).cursor.startSelection();
diff --git a/test/unit/focusBlur.test.js b/test/unit/focusBlur.test.js
index 1bd3af9..27180ae 100644
--- a/test/unit/focusBlur.test.js
+++ b/test/unit/focusBlur.test.js
@@ -1,6 +1,6 @@
suite('focusBlur', function() {
function assertHasFocus(mq, name, invert) {
- assert.ok(!!invert ^ $(mq.el()).find('textarea').is(':focus'), name + (invert ? ' does not have focus' : ' has focus'));
+ assert.ok(!!invert ^ ($(mq.el()).find('textarea')[0] === document.activeElement), name + (invert ? ' does not have focus' : ' has focus'));
}
suite('handlers can shift focus away', function() {
diff --git a/test/unit/publicapi.test.js b/test/unit/publicapi.test.js
index a2c62c3..55645b6 100644
--- a/test/unit/publicapi.test.js
+++ b/test/unit/publicapi.test.js
@@ -744,6 +744,20 @@
$(mq.el()).remove();
});
+ test('integral still has empty limits', function() {
+ var mq = MQ.MathField($('<span>').appendTo('#mock')[0], {
+ sumStartsWithNEquals: true
+ });
+ assert.equal(mq.latex(), '');
+
+ mq.cmd('\\int');
+ assert.equal(mq.latex(), '\\int_{ }^{ }');
+
+ mq.cmd('0');
+ assert.equal(mq.latex(), '\\int_0^{ }', 'cursor in the from block');
+
+ $(mq.el()).remove();
+ });
});
suite('substituteTextarea', function() {
@@ -762,6 +776,58 @@
});
});
+ suite('clickAt', function() {
+ test('inserts at coordinates', function() {
+ // Insert filler so that the page is taller than the window so this test is deterministic
+ // Test that we use clientY instead of pageY
+ var windowHeight = $(window).height();
+ var filler = $('<div>').height(windowHeight);
+ filler.insertBefore('#mock');
+
+ var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
+ mq.typedText("mmmm/mmmm");
+ mq.el().scrollIntoView();
+
+ var box = mq.el().getBoundingClientRect();
+ var clientX = box.left + 30;
+ var clientY = box.top + 30;
+ var target = document.elementFromPoint(clientX, clientY);
+
+ assert.equal(document.activeElement, document.body);
+ mq.clickAt(clientX, clientY, target).write('x');
+ assert.equal(document.activeElement, $(mq.el()).find('textarea')[0]);
+
+ assert.equal(mq.latex(), "\\frac{mmmm}{mmxmm}");
+
+ filler.remove();
+ $(mq.el()).remove();
+ });
+ test('target is optional', function() {
+ // Insert filler so that the page is taller than the window so this test is deterministic
+ // Test that we use clientY instead of pageY
+ var windowHeight = $(window).height();
+ var filler = $('<div>').height(windowHeight);
+ filler.insertBefore('#mock');
+
+ var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
+ mq.typedText("mmmm/mmmm");
+ mq.el().scrollIntoView();
+
+ var box = mq.el().getBoundingClientRect();
+ var clientX = box.left + 30;
+ var clientY = box.top + 30;
+
+ assert.equal(document.activeElement, document.body);
+ mq.clickAt(clientX, clientY).write('x');
+ assert.equal(document.activeElement, $(mq.el()).find('textarea')[0]);
+
+ assert.equal(mq.latex(), "\\frac{mmmm}{mmxmm}");
+
+ filler.remove();
+ $(mq.el()).remove();
+ });
+ });
+
suite('dropEmbedded', function() {
test('inserts into empty', function() {
var mq = MQ.MathField($('<span>').appendTo('#mock')[0]);
@@ -792,7 +858,7 @@
mq.el().scrollIntoView();
- mq.dropEmbedded(mqx + 30, mqy + 40, {
+ mq.dropEmbedded(mqx + 30, mqy + 30, {
htmlString: '<span class="embedded-html"></span>',
text: function () { return "embedded text" },
latex: function () { return "embedded latex" }
diff --git a/test/unit/saneKeyboardEvents.test.js b/test/unit/saneKeyboardEvents.test.js
index 9247803..158d425 100644
--- a/test/unit/saneKeyboardEvents.test.js
+++ b/test/unit/saneKeyboardEvents.test.js
@@ -146,42 +146,42 @@
assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
});
- if (!document.hasFocus()) {
- test('blur in keystroke handler: DOCUMENT NEEDS FOCUS, SEE CONSOLE ');
- console.warn(
- 'The test "blur in keystroke handler" needs the document to have ' +
- 'focus. Only when the document has focus does .select() on an ' +
- 'element also focus it, which is part of the problematic behavior ' +
- 'we are testing robustness against. (Specifically, erroneously ' +
- 'calling .select() in a timeout after the textarea has blurred, ' +
- '"stealing back" focus.)\n' +
- 'Normally, the page being open and focused is enough to have focus, ' +
- 'but with the Developer Tools open, it depends on whether you last ' +
- 'clicked on something in the Developer Tools or on the page itself. ' +
- 'Click the page, or close the Developer Tools, and Refresh.'
- );
- }
- else {
- test('blur in keystroke handler', function(done) {
- var shim = saneKeyboardEvents(el, {
- keystroke: function(key) {
- assert.equal(key, 'Left');
- el[0].blur();
- }
- });
+ test('blur in keystroke handler', function(done) {
+ if (!document.hasFocus()) {
+ console.warn(
+ 'The test "blur in keystroke handler" needs the document to have ' +
+ 'focus. Only when the document has focus does .select() on an ' +
+ 'element also focus it, which is part of the problematic behavior ' +
+ 'we are testing robustness against. (Specifically, erroneously ' +
+ 'calling .select() in a timeout after the textarea has blurred, ' +
+ '"stealing back" focus.)\n' +
+ 'Normally, the page being open and focused is enough to have focus, ' +
+ 'but with the Developer Tools open, it depends on whether you last ' +
+ 'clicked on something in the Developer Tools or on the page itself. ' +
+ 'Click the page, or close the Developer Tools, and Refresh.'
+ );
+ el.remove(); // LOL next line skips teardown https://git.io/vaUWq
+ this.skip();
+ }
- shim.select('foobar');
- assert.ok(document.activeElement === el[0], 'textarea focused');
-
- el.trigger(Event('keydown', { which: 37 }));
- assert.ok(document.activeElement !== el[0], 'textarea blurred');
-
- setTimeout(function() {
- assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
- done();
- });
+ var shim = saneKeyboardEvents(el, {
+ keystroke: function(key) {
+ assert.equal(key, 'Left');
+ el[0].blur();
+ }
});
- }
+
+ shim.select('foobar');
+ assert.ok(document.activeElement === el[0], 'textarea focused');
+
+ el.trigger(Event('keydown', { which: 37 }));
+ assert.ok(document.activeElement !== el[0], 'textarea blurred');
+
+ setTimeout(function() {
+ assert.ok(document.activeElement !== el[0], 'textarea remains blurred');
+ done();
+ });
+ });
suite('selected text after keypress or paste doesn\'t get mistaken' +
' for inputted text', function() {
diff --git a/test/unit/typing.test.js b/test/unit/typing.test.js
index 25628ef..c3c89f1 100644
--- a/test/unit/typing.test.js
+++ b/test/unit/typing.test.js
@@ -56,6 +56,11 @@
mq.typedText('\\asdf+');
assertLatex('\\text{asdf}+');
});
+
+ test('dollar sign', function() {
+ mq.typedText('$');
+ assertLatex('\\$');
+ });
});
suite('auto-expanding parens', function() {
@@ -1034,6 +1039,9 @@
assert.equal(mq.typedText('^').latex(), 'x^{^{ }}');
assert.equal(mq.typedText('2').latex(), 'x^{^2}');
assert.equal(mq.typedText('n').latex(), 'x^{^{2n}}');
+ mq.latex('');
+ assert.equal(mq.typedText('2').latex(), '2');
+ assert.equal(mq.keystroke('Shift-Left').typedText('^').latex(), '^2');
mq.latex('');
MQ.config({ supSubsRequireOperand: true });
@@ -1052,6 +1060,9 @@
assert.equal(mq.typedText('^').latex(), 'x^{ }');
assert.equal(mq.typedText('2').latex(), 'x^2');
assert.equal(mq.typedText('n').latex(), 'x^{2n}');
+ mq.latex('');
+ assert.equal(mq.typedText('2').latex(), '2');
+ assert.equal(mq.keystroke('Shift-Left').typedText('^').latex(), '^2');
});
});
});
diff --git a/test/visual.html b/test/visual.html
index 1c6f44f..f066a7b 100644
--- a/test/visual.html
+++ b/test/visual.html
@@ -24,6 +24,9 @@
td {
width: 33%;
}
+#static-latex-rendering-table td {
+ width: 50%;
+}
#show-textareas-button {
float: right;
}
@@ -77,14 +80,14 @@
<td><span class="mathquill-static-math">\sqrt{\MathQuillMathField{x^2+y^2}}</span>
</table>
-<p>Clicks/mousedown to drag should work anywhere in the blue box: <div class="math-container" style="border: solid 1px lightblue; height: 5em; width: 15em; line-height: 5em; text-align: center"><span class="mathquill-math-field">x_{very\ long\ thing}^2 + a_0 = 0</span></div>
+<p>Touch taps/clicks/mousedown to drag should work anywhere in the blue box: <div class="math-container" style="border: solid 1px lightblue; height: 5em; width: 15em; line-height: 5em; text-align: center; -webkit-tap-highlight-color: rgba(0,0,0,0)"><span class="mathquill-math-field">x_{very\ long\ thing}^2 + a_0 = 0</span></div>
<h3>Redrawing</h3>
<p>
- <span id="reflowing-test">\sqrt{_0^1}</span>
+ <span id="reflowing-test">\sqrt{}</span>
should look the same as
<span class="mathquill-static-math">
- \sqrt{\pi\sqrt\sqrt\frac12\int_0^1}
+ \sqrt{\pi\sqrt\sqrt\frac12}
</span>
</p>
<script type="text/javascript">
@@ -96,7 +99,7 @@
var textarea = $('#reflowing-test textarea');
// paste some stuff that needs resizing
textarea.trigger('paste');
- textarea.val('\\pi\\sqrt{\\sqrt{\\frac12}}\\int');
+ textarea.val('\\pi\\sqrt{\\sqrt{\\frac12}}');
setTimeout(function() { if (count !== 1) throw 'reflow not called'; });
});
</script>
@@ -183,7 +186,7 @@
</table>
<h3>Static LaTeX rendering (<code>.mathquill-static-math</code>) tests</h3>
-<table>
+<table id="static-latex-rendering-table">
<tr><td><span class="mathquill-static-math">^{\frac{as}{ }df}</span><td><span>^{\frac{as}{ }df}</span>
<tr><td><span class="mathquill-static-math">e^{i\pi}+1=0</span><td><span>e^{i\pi}+1=0</span>
<tr><td><span class="mathquill-static-math">\sqrt[n]{1}</span><td><span>\sqrt[n]{1}</span>
@@ -208,6 +211,8 @@
<tr><td><span class="mathquill-static-math">1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}</span><td><span>1+\sum_0^n+\sum_{i=0123}^n+\sum_0^{wordiness}</span>^M
<tr><td><span class="mathquill-static-math">x\ \ \ +\ \ \ y</span><td><span>x\ \ \ +\ \ \ y</span>^M
<tr><td><span class="mathquill-static-math">\sum _{n=0}^3\cos x</span><td><span>\sum _{n=0}^3\cos x</span>^M
+<tr><td><span class="mathquill-static-math">\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi </span><td><span>\int _{\phi =0}^{2\pi }\int _{\theta =0}^{\pi }\int _{r=0}^{\infty }f(r,\theta ,\phi )r^2\sin \theta drd\theta d\phi </span>
+<tr><td><span class="mathquill-static-math">\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}</span><td><span>\int_0^{\frac{\frac{1}{2}}{3}} \int_0^{\frac{1}{\frac{2}{3}}} \int_0^{\frac{1}{\frac{2}{\frac{3}{\frac{4}{5}}}}}</span>
<tr><td colspan=2><span id="sixes"></span>
<script>
$(function() {
@@ -297,7 +302,25 @@
// test selecting from outside the mathquill editable
var $mq = $('.math-container .mathquill-math-field');
$('.math-container').mousedown(function(e) {
- if (!jQuery.contains($mq[0], e.target)) $mq.triggerHandler(e);
+ if (e.target === $mq[0] || $.contains($mq[0], e.target)) return;
+ $mq.triggerHandler(e);
+})
+// test API for "fast touch taps" #622 & #403
+.on('touchstart', function() {
+ var moved = false;
+ $(this).on('touchmove.tmp', function() { moved = true; })
+ .on('touchend.tmp', function(e) {
+ $(this).off('.tmp');
+ MathQuill($mq[0]).ignoreNextMousedown(function() {
+ return Date.now() < e.timeStamp + 1000;
+ });
+ if (moved) return; // note that this happens after .ignoreNextMousedown()
+ // because even if the touch gesture doesn't 'count' as a tap to us,
+ // we still want to suppress the legacy mouse events, else we'd react
+ // fast to some taps and slow to others, that'd be weird
+ var touch = e.originalEvent.changedTouches[0];
+ MathQuill($mq[0]).clickAt(touch.clientX, touch.clientY, touch.target);
+ });
});
// Selection Tests