Merge pull request #580 from mathquill/build.nodejs-vs-node
Makefile nice err msg if no Node or installed wrong
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d2d549..4a787ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,40 @@
+## v0.10.1: 2016-03-21
+
+Important fix: remove `font-size: 0` on textarea (#585), fixing typing
+in Chrome Canary (#540) as well as the Enter key not triggering the
+`enter` handler in Webkit and Blink (#566). `transform: scale(0)` is
+used instead and expected to be much more robust.
+
+(Note: if you're coming from v0.9.x, there've been major API changes,
+see the [v0.9.x → v0.10.0 Migration Guide][].)
+
+[v0.9.x → v0.10.0 Migration Guide]: https://github.com/mathquill/mathquill/wiki/v0.9.x-%E2%86%92-v0.10.0-Migration-Guide
+
+**new features:**
+- (#544, #552, #558, #581) new symbols `\nparallel`, `\measuredangle`,
+ `\odot`, `\parallelogram` (nonstandard), `\nless`, `\ngtr`, `\square`
+- (#544) new commands `\overleftarrow`, `\overrightarrow`
+
+
+**bugfixes:**
+- (#585) fix typing in Chrome Canary, Enter key in Webkit+Blink
+- (#582) fix `\degree` symbol to round-trip (rather than exporting
+ `^\circ` which doesn't parse as one symbol)
+- (#578) fix `.text()` to output `\cdot` as `*`
+- (#529, #571, #574) fix `.text()` of fractions, spaces, variables followed
+ by exponents
+- (#577) fix `\triangle` symbol to match LaTeX better
+- (#568) hotfix #435 order-dependence breaking clean build on Linux
+- (#560) fix florin spacing still too close
+- (#546) fix parsing or pasting `×` (Unicode times symbol)
+- (#519/#487) fix auto-horizontal-scroll/pan on API calls
+- (#528) fix #429 can't move cursor out of `TextBlock`
+- (#526) fix exponentiation to export `^` not `**`
+- (#525) fix Tab while there's a selection
+
+**build system fixes:**
+- (#532) add console output to show URL of local test pages
+
## v0.10.0: 2016-02-20
Many major changes including a total overhaul of the API (no more
diff --git a/Makefile b/Makefile
index 7ce1207..8acc2a6 100644
--- a/Makefile
+++ b/Makefile
@@ -75,8 +75,9 @@
CLEAN += $(BUILD_DIR)/*
DISTDIR = ./mathquill-$(VERSION)
-DIST = $(DISTDIR).tgz
-CLEAN += $(DIST)
+DISTTAR = $(DISTDIR).tgz
+DISTZIP = $(DISTDIR).zip
+CLEAN += $(DISTTAR) $(DISTZIP)
# programs and flags
UGLIFY ?= ./node_modules/.bin/uglifyjs
@@ -111,7 +112,6 @@
uglify: $(UGLY_JS)
css: $(BUILD_CSS)
font: $(FONT_TARGET)
-dist: $(DIST)
clean:
rm -rf $(CLEAN)
@@ -136,7 +136,7 @@
$(LESSC) --modify-var="basic=true" $(LESS_OPTS) $(CSS_MAIN) > $@
$(NODE_MODULES_INSTALLED): package.json
- npm install
+ NODE_ENV=development npm install
touch $(NODE_MODULES_INSTALLED)
$(BUILD_DIR_EXISTS):
@@ -147,10 +147,11 @@
rm -rf $@
cp -r $< $@
-$(DIST): $(UGLY_JS) $(BUILD_JS) $(BUILD_CSS) $(FONT_TARGET)
+dist: $(UGLY_JS) $(BUILD_JS) $(BUILD_CSS) $(FONT_TARGET)
rm -rf $(DISTDIR)
cp -r $(BUILD_DIR) $(DISTDIR)
- tar -czf $(DIST) --exclude='\.gitkeep' $(DISTDIR)
+ zip -r -X $(DISTZIP) $(DISTDIR)
+ tar -czf $(DISTTAR) $(DISTDIR)
rm -r $(DISTDIR)
#
@@ -166,83 +167,3 @@
$(BUILD_TEST): $(INTRO) $(SOURCES_FULL) $(UNIT_TESTS) $(OUTRO) $(BUILD_DIR_EXISTS)
cat $^ > $@
-
-#
-# -*- site (mathquill.github.com) tasks
-#
-
-.PHONY: site publish site-pull
-
-SITE = mathquill.github.com
-SITE_CLONE_URL = git@github.com:mathquill/mathquill.github.com
-SITE_COMMITMSG = 'updating mathquill to $(VERSION)'
-
-DOWNLOADS_PAGE = $(SITE)/downloads.html
-DIST_DOWNLOAD = $(SITE)/downloads/$(DIST)
-
-site: $(SITE) $(SITE)/mathquill $(SITE)/demo.html $(SITE)/support $(DOWNLOADS_PAGE)
-
-publish: site-pull site
- pwd
- cd $(SITE) \
- && git add -- mathquill demo.html support downloads downloads.html \
- && git commit -m $(SITE_COMMITMSG) \
- && git push
-
-$(SITE)/mathquill: $(DIST)
- mkdir -p $@
- tar -xzf $(DIST) \
- --directory $@ \
- --strip-components=2
-
-$(DIST_DOWNLOAD): $(DIST)
- mkdir -p $(dir $@)
- cp $^ $@
-
-# freaking bsd, i swear
-# adapted from https://developer.apple.com/library/mac/documentation/opensource/Conceptual/ShellScripting/PortingScriptstoMacOSX/PortingScriptstoMacOSX.html#//apple_ref/doc/uid/TP40004268-TP40003517-SW21
-ifeq (x, $(shell echo xy | sed -r 's/(x)y/\1/' 2>/dev/null))
- # gnu
- SED = sed -r
- SED_I = $(SED) -i
-else
- # bsd
- SED = sed -E
- SED_I = $(SED) -i ''
-endif
-
-$(DOWNLOADS_PAGE): $(DIST_DOWNLOAD)
- @echo Using $(SED)
- @echo -n updating downloads page...
- @$(SED_I) \
- -e '/Latest version:/ s/[0-9]+[.][0-9]+[.][0-9]+/$(VERSION)/g' \
- $(DOWNLOADS_PAGE)
- @mkdir -p tmp
- @ls $(SITE)/downloads/*.tgz \
- | egrep -o '[0-9]+[.][0-9]+[.][0-9]+' \
- | fgrep -v $(VERSION) \
- | sort -rn -t. -k 1,1 -k 2,2 -k 3,3 \
- | sed 's|.*|<li><a class="prev" href="downloads/mathquill-&.tgz">v&</a></li>|' \
- > tmp/versions-list.html
- @$(SED_I) \
- -e '/<a class="prev"/d' \
- -e '/<ul id="prev-versions">/ r tmp/versions-list.html' \
- $(DOWNLOADS_PAGE)
- @rm tmp/versions-list.html
- @echo done.
-
-$(SITE)/demo.html: test/demo.html
- cat $^ \
- | $(SED) 's:../build/:mathquill/:' \
- | $(SED) 's:local test page:live demo:' \
- > $@
-
-$(SITE)/support: test/support
- rm -rf $@
- cp -r $^ $@
-
-$(SITE):
- git clone $(SITE_CLONE_URL) $@
-
-site-pull: $(SITE)
- cd $(SITE) && git pull
diff --git a/README.md b/README.md
index 7b93213..ff01a2d 100644
--- a/README.md
+++ b/README.md
@@ -144,6 +144,7 @@
Additionally, descendants of `MQ.EditableField` (currently only `MQ.MathField`)
expose:
+* `.focus()`, `.blur()` focuses or defocuses the editable field
* `.write(' - 1')` will write some LaTeX at the current cursor position
* `.cmd('\\sqrt')` will enter a LaTeX command at the current cursor position or
with the current selection
@@ -279,10 +280,11 @@
`autoOperatorNames`, a list of the same form (space-delimited letters-only each
length>=2), and overrides the set of operator names that automatically become
non-italicized when typing the letters without typing a backslash first, like
-`sin`, `log`, etc. (Defaults to [the LaTeX built-in operator names][Wikia], but
-with additional trig operators like `sech`, `arcsec`, `arsinh`, etc.)
+`sin`, `log`, etc. Defaults to the LaTeX built-in operator names ([Section
+3.17 of the Short Math Guide][3.17]) plus some missing trig operators like
+`sech`, `arcsec`, `arsinh`, etc.
-[Wikia]: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
+[3.17]: http://tinyurl.com/jm9okjc
`substituteTextarea`, a function that creates a focusable DOM element, called
when setting up a math field. It defaults to `<textarea autocorrect=off .../>`,
diff --git a/package.json b/package.json
index 703e3ec..9c8fb28 100644
--- a/package.json
+++ b/package.json
@@ -1,17 +1,17 @@
{
"name": "mathquill",
"description": "Easily type math in your webapp",
- "version": "0.10.0",
+ "version": "0.10.1",
"license": "MPL-2.0",
"repository": {
"type": "git",
"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/advancedSymbols.js b/src/commands/math/advancedSymbols.js
index a4dccb4..f5aa5a5 100644
--- a/src/commands/math/advancedSymbols.js
+++ b/src/commands/math/advancedSymbols.js
@@ -197,6 +197,7 @@
LatexCmds.spadesuit = bind(VanillaSymbol, '\\spadesuit ', '♠');
//not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
LatexCmds.parallelogram = bind(VanillaSymbol, '\\parallelogram ', '▱');
+LatexCmds.square = bind(VanillaSymbol, '\\square ', '⬜');
//variable-sized
LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '∮');
@@ -221,10 +222,6 @@
LatexCmds.rbrack = bind(VanillaSymbol, ']');
//various symbols
-LatexCmds['∫'] =
-LatexCmds['int'] =
-LatexCmds.integral = bind(Symbol,'\\int ','<big>∫</big>');
-
LatexCmds.slash = bind(VanillaSymbol, '/');
LatexCmds.vert = bind(VanillaSymbol,'|');
LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','⊥');
@@ -302,6 +299,9 @@
LatexCmds.xist = //LOL
LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
bind(VanillaSymbol,'\\exists ','∃');
+
+LatexCmds.nexists = LatexCmds.nexist =
+ bind(VanillaSymbol, '\\nexists ', '∄');
LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
bind(VanillaSymbol,'\\wedge ','∧');
@@ -319,7 +319,8 @@
LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection =
bind(BinaryOperator,'\\cap ','∩');
-LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'^\\circ ','°');
+// FIXME: the correct LaTeX would be ^\circ but we can't parse that
+LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'\\degree ','°');
LatexCmds.ang = LatexCmds.angle = bind(VanillaSymbol,'\\angle ','∠');
LatexCmds.measuredangle = bind(VanillaSymbol,'\\measuredangle ','∡');
diff --git a/src/commands/math/basicSymbols.js b/src/commands/math/basicSymbols.js
index ec15b9a..a8578ae 100644
--- a/src/commands/math/basicSymbols.js
+++ b/src/commands/math/basicSymbols.js
@@ -59,26 +59,27 @@
var Letter = P(Variable, function(_, super_) {
_.init = function(ch) { return super_.init.call(this, this.letter = ch); };
_.createLeftOf = function(cursor) {
+ super_.createLeftOf.apply(this, arguments);
var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength;
if (maxLength > 0) {
// want longest possible autocommand, so join together longest
// sequence of letters
- var str = this.letter, l = cursor[L], i = 1;
- while (l instanceof Letter && i < maxLength) {
+ var str = '', l = this, i = 0;
+ // FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name
+ while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) {
str = l.letter + str, l = l[L], i += 1;
}
// check for an autocommand, going thru substrings longest to shortest
while (str.length) {
if (autoCmds.hasOwnProperty(str)) {
- for (var i = 2, l = cursor[L]; i < str.length; i += 1, l = l[L]);
- Fragment(l, cursor[L]).remove();
+ for (var i = 1, l = this; i < str.length; i += 1, l = l[L]);
+ Fragment(l, this).remove();
cursor[L] = l[L];
return LatexCmds[str](str).createLeftOf(cursor);
}
str = str.slice(1);
}
}
- super_.createLeftOf.apply(this, arguments);
};
_.italicize = function(bool) {
this.isItalic = bool;
@@ -103,7 +104,7 @@
// removeClass and delete flags from all letters before figuring out
// which, if any, are part of an operator name
Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) {
- el.italicize(true).jQ.removeClass('mq-first mq-last');
+ el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub');
el.ctrlSeq = el.letter;
});
@@ -122,8 +123,21 @@
first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq;
last.ctrlSeq += (isBuiltIn ? ' ' : '}');
if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last');
- if (nonOperatorSymbol(first[L])) first.jQ.addClass('mq-first');
- if (nonOperatorSymbol(last[R])) last.jQ.addClass('mq-last');
+ if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first');
+ if (!shouldOmitPadding(last[R])) {
+ if (last[R] instanceof SupSub) {
+ var supsub = last[R]; // XXX monkey-patching, but what's the right thing here?
+ // Have operatorname-specific code in SupSub? A CSS-like language to style the
+ // math tree, but which ignores cursor and selection (which CSS can't)?
+ var respace = supsub.siblingCreated = supsub.siblingDeleted = function() {
+ supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket));
+ };
+ respace();
+ }
+ else {
+ last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket));
+ }
+ }
i += len - 1;
first = last;
@@ -132,12 +146,13 @@
}
}
};
- function nonOperatorSymbol(node) {
- return node instanceof Symbol && !(node instanceof BinaryOperator);
+ function shouldOmitPadding(node) {
+ // omit padding if no node, or if node already has padding (to avoid double-padding)
+ return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation);
}
});
var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that
- // are built-into LaTeX: http://latex.wikia.com/wiki/List_of_LaTeX_symbols#Named_operators:_sin.2C_cos.2C_etc.
+ // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc
// MathQuill auto-unitalicizes some operator names not in that set, like 'hcf'
// and 'arsinh', which must be exported as \operatorname{hcf} and
// \operatorname{arsinh}. Note: over/under line/arrow \lim variants like
@@ -407,8 +422,11 @@
_.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) {
if (dir === R) return; // ignore if sibling only changed on the right
+ // If the left sibling is a binary operator or a separator (comma, semicolon, colon)
+ // or an open bracket (open parenthesis, open square bracket)
+ // consider the operator to be unary, otherwise binary
this.jQ[0].className =
- (!this[L] || this[L] instanceof BinaryOperator ? '' : 'mq-binary-operator');
+ (!this[L] || this[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(this[L].ctrlSeq) ? '' : 'mq-binary-operator');
return this;
};
});
@@ -422,7 +440,7 @@
bind(PlusMinus,'\\mp ','∓');
CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot =
- bind(BinaryOperator, '\\cdot ', '·');
+ bind(BinaryOperator, '\\cdot ', '·', '*');
//semantically should be ⋅, but · looks better
var Inequality = P(BinaryOperator, function(_, super_) {
diff --git a/src/commands/math/commands.js b/src/commands/math/commands.js
index 37c00bf..f4e1381 100644
--- a/src/commands/math/commands.js
+++ b/src/commands/math/commands.js
@@ -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;
@@ -380,6 +375,26 @@
LatexCmds.coprod =
LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','∐');
+LatexCmds['∫'] =
+LatexCmds['int'] =
+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 =
LatexCmds.dfrac =
diff --git a/src/commands/text.js b/src/commands/text.js
index 42f85d4..59614ee 100644
--- a/src/commands/text.js
+++ b/src/commands/text.js
@@ -48,10 +48,8 @@
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?
+ if (text.length === 0) return Fragment();
+
TextPiece(text).adopt(textBlock, 0, 0);
return textBlock;
})
@@ -64,7 +62,11 @@
});
};
_.text = function() { return '"' + this.textContents() + '"'; };
- _.latex = function() { return '\\text{' + this.textContents() + '}'; };
+ _.latex = function() {
+ var contents = this.textContents();
+ if (contents.length === 0) return '';
+ return '\\text{' + contents + '}';
+ };
_.html = function() {
return (
'<span class="mq-text-mode" mathquill-command-id='+this.id+'>'
@@ -107,7 +109,7 @@
else { // split apart
var leftBlock = TextBlock();
var leftPc = this.ends[L];
- leftPc.disown();
+ leftPc.disown().jQ.detach();
leftPc.adopt(leftBlock, 0, 0);
cursor.insLeftOf(this);
@@ -162,15 +164,22 @@
}
};
- _.blur = function() {
+ _.blur = function(cursor) {
MathBlock.prototype.blur.call(this);
- fuseChildren(this);
+ if (!cursor) return;
+ if (this.textContents() === '') {
+ this.remove();
+ if (cursor[L] === this) cursor[L] = this[L];
+ else if (cursor[R] === this) cursor[R] = this[R];
+ }
+ else fuseChildren(this);
};
function fuseChildren(self) {
self.jQ[0].normalize();
var textPcDom = self.jQ[0].firstChild;
+ if (!textPcDom) return;
pray('only node in TextBlock span is Text node', textPcDom.nodeType === 3);
// nodeType === 3 has meant a Text node since ancient times:
// http://reference.sitepoint.com/javascript/Node/nodeType
@@ -287,7 +296,6 @@
};
});
-CharCmds.$ =
LatexCmds.text =
LatexCmds.textnormal =
LatexCmds.textrm =
diff --git a/src/css/main.less b/src/css/main.less
index a56bece..25dd7b4 100644
--- a/src/css/main.less
+++ b/src/css/main.less
@@ -1,5 +1,5 @@
/*
- * MathQuill v0.10.0 http://mathquill.com
+ * MathQuill v0.10.1 http://mathquill.com
* by Han, Jeanine, and Mary maintainers@mathquill.com
*
* This Source Code Form is subject to the terms of the
diff --git a/src/css/math.less b/src/css/math.less
index 6115ee8..aae409d 100644
--- a/src/css/math.less
+++ b/src/css/math.less
@@ -49,7 +49,14 @@
.mq-text-mode {
- font-size: 87%;
+ display: inline-block;
+ }
+ .mq-text-mode.mq-hasCursor {
+ box-shadow: inset darkgray 0 .1em .2em;
+ padding: 0 .1em;
+ margin: 0 -.1em;
+
+ min-width: 1ex;
}
.mq-font {
@@ -80,7 +87,29 @@
}
big {
- font-size: 125%;
+ 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 {
@@ -122,12 +151,9 @@
// its contents, rather than always being as wide as the subscript.
// See also .fraction
.mq-supsub {
+ text-align: left;
font-size: 90%;
vertical-align: -.5em;
- &.mq-limit {
- font-size: 80%;
- vertical-align: -.4em;
- }
&.mq-sup-only {
vertical-align: .5em;
@@ -146,9 +172,6 @@
display: block;
float: left;
}
- &.mq-limit .mq-sub {
- margin-left: -.25em;
- }
.mq-binary-operator {
padding: 0 .1em;
@@ -208,7 +231,7 @@
var.mq-operator-name.mq-first {
padding-left: .2em;
}
- var.mq-operator-name.mq-last {
+ var.mq-operator-name.mq-last, .mq-supsub.mq-after-operator-name {
padding-right: .2em;
}
@@ -284,6 +307,8 @@
}
.mq-large-operator {
+ vertical-align: -.2em;
+ padding: .2em;
text-align: center;
.mq-from, big, .mq-to {
diff --git a/src/css/mixins/css3.less b/src/css/mixins/css3.less
index 2aecf28..b53bf6b 100644
--- a/src/css/mixins/css3.less
+++ b/src/css/mixins/css3.less
@@ -5,6 +5,13 @@
-o-transform-origin: @arguments;
transform-origin: @arguments;
}
+.transform (...) {
+ -webkit-transform: @arguments;
+ -moz-transform: @arguments;
+ -ms-transform: @arguments;
+ -o-transform: @arguments;
+ transform: @arguments;
+}
.user-select (...) {
-webkit-user-select: @arguments;
diff --git a/src/css/textarea.less b/src/css/textarea.less
index 487e1f1..1484fa7 100644
--- a/src/css/textarea.less
+++ b/src/css/textarea.less
@@ -14,7 +14,7 @@
position: absolute; // the only way to hide the textarea *and* the
clip: rect(1em 1em 1em 1em); // blinking insertion point in IE
- font-size: 0; // the only way to hide the blinking blue cursor in iOS 8
+ .transform(scale(0)); // the only way to hide the blinking blue cursor in iOS 8 #584
resize: none; // hotfix: https://code.google.com/p/chromium/issues/detail?id=355199#c1
diff --git a/src/cursor.js b/src/cursor.js
index b08b2f6..622a023 100644
--- a/src/cursor.js
+++ b/src/cursor.js
@@ -56,7 +56,8 @@
this[-dir] = oppDir;
// by contract, .blur() is called after all has been said and done
// and the cursor has actually been moved
- if (oldParent !== parent && oldParent.blur) oldParent.blur();
+ // FIXME pass cursor to .blur() so text can fix cursor pointers when removing itself
+ if (oldParent !== parent && oldParent.blur) oldParent.blur(this);
};
_.insDirOf = function(dir, el) {
prayDirection(dir);
diff --git a/src/font/Symbola.woff b/src/font/Symbola.woff
old mode 100755
new mode 100644
index b9bba23..858ca28
--- a/src/font/Symbola.woff
+++ b/src/font/Symbola.woff
Binary files differ
diff --git a/src/font/Symbola.woff2 b/src/font/Symbola.woff2
old mode 100755
new mode 100644
index 9d3e820..07a752d
--- a/src/font/Symbola.woff2
+++ b/src/font/Symbola.woff2
Binary files differ
diff --git a/src/intro.js b/src/intro.js
index e1d9379..654b8cd 100644
--- a/src/intro.js
+++ b/src/intro.js
@@ -1,5 +1,5 @@
/**
- * MathQuill v0.10.0 http://mathquill.com
+ * MathQuill v0.10.1 http://mathquill.com
* by Han, Jeanine, and Mary maintainers@mathquill.com
*
* This Source Code Form is subject to the terms of the
diff --git a/src/publicapi.js b/src/publicapi.js
index 45b05e5..2ccdeb2 100644
--- a/src/publicapi.js
+++ b/src/publicapi.js
@@ -15,7 +15,7 @@
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. You can fix this easily by doing this before ' +
+ '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' +
@@ -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/latex.js b/src/services/latex.js
index 025f1a8..75369e8 100644
--- a/src/services/latex.js
+++ b/src/services/latex.js
@@ -1,6 +1,6 @@
-// Parser MathCommand
+// Parser MathBlock
var latexMathParser = (function() {
- function commandToBlock(cmd) {
+ function commandToBlock(cmd) { // can also take in a Fragment
var block = MathBlock();
cmd.adopt(block, 0, 0);
return block;
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/backspace.test.js b/test/unit/backspace.test.js
index 51a34ca..688d1ba 100644
--- a/test/unit/backspace.test.js
+++ b/test/unit/backspace.test.js
@@ -197,7 +197,7 @@
assert.equal(mq.latex(),'n=1');
});
- test('backspace into text block', function() {
+ test('backspace through text block', function() {
mq.latex('\\text{x}');
mq.keystroke('Backspace');
@@ -206,6 +206,18 @@
assert.equal(cursor.parent, textBlock, 'cursor is in text block');
assert.equal(cursor[R], 0, 'cursor is at the end of text block');
assert.equal(cursor[L].text, 'x', 'cursor is rightward of the x');
+ assert.equal(mq.latex(), '\\text{x}', 'the x has been deleted');
+
+ mq.keystroke('Backspace');
+ assert.equal(cursor.parent, textBlock, 'cursor is still in text block');
+ assert.equal(cursor[R], 0, 'cursor is at the right end of the text block');
+ assert.equal(cursor[L], 0, 'cursor is at the left end of the text block');
+ assert.equal(mq.latex(), '', 'the x has been deleted');
+
+ mq.keystroke('Backspace');
+ assert.equal(cursor[R], 0, 'cursor is at the right end of the root block');
+ assert.equal(cursor[L], 0, 'cursor is at the left end of the root block');
+ assert.equal(mq.latex(), '');
});
suite('empties', function() {
diff --git a/test/unit/css.test.js b/test/unit/css.test.js
index 223895f..639cdf9 100644
--- a/test/unit/css.test.js
+++ b/test/unit/css.test.js
@@ -52,5 +52,58 @@
var mqF = $(mq.el()).find('.mq-f');
var testVal = parseFloat(mqF.css('margin-right')) - parseFloat(mqF.css('margin-left'));
assert.ok(testVal > 0, 'this should be truthy') ;
+
+ $(mq.el()).remove();
+ });
+
+ test('unary PlusMinus before separator', function () {
+ var mq = MQ.MathField($('<span></span>').appendTo('#mock')[0]);
+ mq.latex('(-1,-1-1)-1,(+1;+1+1)+1,(\\pm1,\\pm1\\pm1)\\pm1');
+ var spans = $(mq.el()).find('.mq-root-block').find('span');
+ assert.equal(spans.length, 35, 'PlusMinus expression parsed incorrectly');
+
+ function isBinaryOperator(i) { return $(spans[i]).hasClass('mq-binary-operator'); }
+ function assertBinaryOperator(i, s) { assert.ok(isBinaryOperator(i), '"' + s + '" should be binary'); }
+ function assertUnaryOperator(i, s) { assert.ok(!isBinaryOperator(i), '"' + s + '" should be unary'); }
+
+ assertUnaryOperator(1, '(-');
+ assertUnaryOperator(4, '(-1,-');
+ assertBinaryOperator(6, '(-1,-1-');
+ assertBinaryOperator(9, '(-1,-1-1)-');
+ assertUnaryOperator(13, '(-1,-1-1)-1,(+');
+ assertUnaryOperator(16, '(-1,-1-1)-1,(+1;+');
+ assertBinaryOperator(18, '(-1,-1-1)-1,(+1;+1+');
+ assertBinaryOperator(21, '(-1,-1-1)-1,(+1;+1+1)+');
+ assertUnaryOperator(25, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm');
+ assertUnaryOperator(28, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm');
+ assertBinaryOperator(30, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm1\pm');
+ assertBinaryOperator(33, '(-1,-1-1)-1,(+1;+1+1)+1,(\pm1,\pm1\pm1)\pm');
+
+ $(mq.el()).remove();
+ });
+
+ test('operator name spacing e.g. sin x', function() {
+ var mq = MathQuill.MathField($('<span></span>').appendTo(mock)[0]);
+
+ mq.typedText('sin');
+ var n = jQuery('#mock var.mq-operator-name:last');
+ assert.equal(n.text(), 'n');
+ assert.ok(!n.is('.mq-last'));
+
+ mq.typedText('x');
+ assert.ok(n.is('.mq-last'));
+
+ mq.keystroke('Left').typedText('(');
+ assert.ok(!n.is('.mq-last'));
+
+ mq.keystroke('Backspace').typedText('^');
+ assert.ok(!n.is('.mq-last'));
+ var supsub = jQuery('#mock .mq-supsub');
+ assert.ok(supsub.is('.mq-after-operator-name'));
+
+ mq.typedText('2').keystroke('Tab').typedText('(');
+ assert.ok(!supsub.is('.mq-after-operator-name'));
+
+ $(mq.el()).empty();
});
});
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/latex.test.js b/test/unit/latex.test.js
index 5795b18..4cf59fa 100644
--- a/test/unit/latex.test.js
+++ b/test/unit/latex.test.js
@@ -100,11 +100,14 @@
assertParsesLatex('\\text { lol! } ', '\\text{ lol! }');
assertParsesLatex('\\text{apples} \\ne \\text{oranges}',
'\\text{apples}\\ne \\text{oranges}');
+ assertParsesLatex('\\text{}', '');
});
test('not real LaTex commands, but valid symbols', function() {
assertParsesLatex('\\parallelogram ');
assertParsesLatex('\\circledot ', '\\odot ');
+ assertParsesLatex('\\degree ');
+ assertParsesLatex('\\square ');
});
suite('public API', function() {
diff --git a/test/unit/publicapi.test.js b/test/unit/publicapi.test.js
index efeb9c2..91ef1f0 100644
--- a/test/unit/publicapi.test.js
+++ b/test/unit/publicapi.test.js
@@ -124,7 +124,7 @@
mq.typedText('qrt');
assert.equal(mq.text(), '\\sqrt');
});
-
+
test('.text() with complete commands', function() {
mq.latex('\\sqrt{}');
assert.equal(mq.text(), 'sqrt()');
@@ -145,7 +145,11 @@
mq.latex('3x+\\ 4');
assert.equal(mq.text(), '3*x+ 4');
mq.latex('x^2');
- assert.equal(mq.text(), 'x^2')
+ assert.equal(mq.text(), 'x^2');
+
+ mq.latex('');
+ mq.typedText('*2*3***4');
+ assert.equal(mq.text(), '*2*3***4');
});
test('.moveToDirEnd(dir)', function() {
@@ -321,6 +325,8 @@
assert.equal(mq.latex(), 'xy^2');
mq.keystroke('Right Shift-Left Shift-Left Shift-Left').cmd('\\sqrt');
assert.equal(mq.latex(), '\\sqrt{xy^2}');
+ mq.typedText('*2**');
+ assert.equal(mq.latex(), '\\sqrt{xy^2\\cdot2\\cdot\\cdot}');
});
test('backslash commands are passed their name', function() {
@@ -738,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() {
@@ -756,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 + 40;
+ 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 + 40;
+
+ 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]);
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/text.test.js b/test/unit/text.test.js
index 6f2ecd3..982a5ac 100644
--- a/test/unit/text.test.js
+++ b/test/unit/text.test.js
@@ -65,4 +65,43 @@
assert.equal(block.latex(), '\\text{x}');
});
+
+ test('stepping out of an empty block deletes it', function() {
+ var mq = MathQuill.MathField($('<span></span>').appendTo('#mock')[0]);
+ var controller = mq.__controller;
+ var cursor = controller.cursor;
+
+ try {
+ mq.latex('\\text{x}');
+
+ mq.keystroke('Left');
+ assertSplit(cursor.jQ, 'x');
+
+ mq.keystroke('Backspace');
+ assertSplit(cursor.jQ);
+
+ mq.keystroke('Right');
+ assertSplit(cursor.jQ);
+ assert.equal(cursor[L], 0);
+ } finally {
+ $(mq.el()).remove();
+ }
+ });
+
+ test('typing $ in a textblock splits it', function() {
+ var mq = MathQuill.MathField($('<span></span>').appendTo('#mock')[0]);
+ var controller = mq.__controller;
+ var cursor = controller.cursor;
+
+ try {
+ mq.latex('\\text{asdf}');
+ mq.keystroke('Left Left Left');
+ assertSplit(cursor.jQ, 'as', 'df');
+
+ mq.typedText('$');
+ assert.equal(mq.latex(), '\\text{as}\\text{df}');
+ } finally {
+ $(mq.el()).remove();
+ }
+ });
});
diff --git a/test/unit/typing.test.js b/test/unit/typing.test.js
index 3ba3b31..737d73f 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() {
@@ -813,6 +818,7 @@
suite('autoCommands', function() {
setup(function() {
MQ.config({
+ autoOperatorNames: 'sin pp',
autoCommands: 'pi tau phi theta Gamma sum prod sqrt nthroot'
});
});
@@ -880,6 +886,13 @@
assertLatex('\\sin\\pi');
});
+ test('has lower "precedence" than operator names', function() {
+ mq.typedText('ppi');
+ assertLatex('\\operatorname{pp}i');
+ mq.keystroke('Left Left').typedText('i');
+ assertLatex('\\pi pi');
+ });
+
test('command contains non-letters', function() {
assert.throws(function() { MQ.config({ autoCommands: 'e1' }); });
});
diff --git a/test/visual.html b/test/visual.html
index 1a47eca..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">a_2 x^2 + a_1 x + 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,11 +186,22 @@
</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>
-<tr><td><span class="mathquill-static-math">\theta + \sin 2\deg</span><td><span>\theta + \sin 2\deg</span>
+<tr><td><span class="mathquill-static-math">\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)</span></td><td><span>\sin ^2x+\sin ^2\left(x\right)+\sin ^2(x)</span>
+<tr><td><span class="mathquill-static-math">12a\sin b</span></td><td><span>12a\sin b</span>
+<tr><td><span class="mathquill-static-math">1a^2 \sin b</span></td><td><span>1a^2 \sin b</span>
+<tr><td><span class="mathquill-static-math">a + \sin b</span></td><td><span>a + \sin b</span>
+<tr><td><span class="mathquill-static-math">a + b</span></td><td><span>a + b</span>
+<tr><td><span class="mathquill-static-math">\sum\sin</span></td><td><span>\sum\sin</span>
+<tr><td><span class="mathquill-static-math">\sum a</span></td><td><span>\sum a</span>
+<tr><td><span class="mathquill-static-math">(\sin)</span></td><td><span>(\sin)</span>
+<tr><td><span class="mathquill-static-math">\left(\sin\right)</span></td><td><span>\left(\sin\right)</span>
+<tr><td><span class="mathquill-static-math">(x)\sin(x)</span></td><td><span>(x)\sin(x)</span>
+<tr><td><span class="mathquill-static-math">\left(x\right)\sin\left(x\right)</span></td><td><span>\left(x\right)\sin\left(x\right)</span>
+<tr><td><span class="mathquill-static-math">a \sin b</span></td><td><span>a \sin b</span>
<tr><td><span class="mathquill-static-math">-1 + +-2</span><td><span>-1 + +-2</span>
<tr><td><span class="mathquill-static-math">\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}</span><td><span>\left ( n+1 \right ) + \frac{1}{\frac{n}{k}} + \binom{n}{k}</span>
<tr><td><span class="mathquill-static-math">x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}}</span><td><span>x_{\frac{1}{\frac{2}{3}}}^{\frac{\frac{1}{2}}{3}}</span>
@@ -197,6 +211,22 @@
<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() {
+ var i,
+ sixes = '',
+ start = 10,
+ end = 40;
+ for (i = start; i <= end; i++) {
+ sixes += '<span style="font-size: ' + i + 'px" class="mathquill-math-field">6<\/span>';
+ }
+ $('#sixes').html(sixes);
+ $('#sixes .mathquill-math-field').each(function() { MQ.MathField(this); });
+});
+</script>
</table>
<p id="paren-alignment">Parentheses vertical alignment at font sizes ranging from 10px to 24px: <button>Click Me</button></p>
@@ -272,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