Merge pull request #598 from Learnosity/woff-fonts-update

[LRN] Added WOFF and WOFF2 fonts built from OTF
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 39df479..dcb2497 100644
--- a/Makefile
+++ b/Makefile
@@ -56,8 +56,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
@@ -92,7 +93,6 @@
 uglify: $(UGLY_JS)
 css: $(BUILD_CSS)
 font: $(FONT_TARGET)
-dist: $(DIST)
 clean:
 	rm -rf $(CLEAN)
 
@@ -117,7 +117,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):
@@ -128,10 +128,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)
 
 #
@@ -147,83 +148,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..956bee5 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
diff --git a/package.json b/package.json
index 703e3ec..e9889de 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "mathquill",
   "description": "Easily type math in your webapp",
-  "version": "0.10.0",
+  "version": "0.10.1",
   "license": "MPL-2.0",
   "repository": {
     "type": "git",
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 ', '&#9824;');
 //not real LaTex command see https://github.com/mathquill/mathquill/pull/552 for more details
 LatexCmds.parallelogram = bind(VanillaSymbol, '\\parallelogram ', '&#9649;');
+LatexCmds.square = bind(VanillaSymbol, '\\square ', '&#11036;');
 
 //variable-sized
 LatexCmds.oint = bind(VanillaSymbol, '\\oint ', '&#8750;');
@@ -221,10 +222,6 @@
 LatexCmds.rbrack = bind(VanillaSymbol, ']');
 
 //various symbols
-LatexCmds['∫'] =
-LatexCmds['int'] =
-LatexCmds.integral = bind(Symbol,'\\int ','<big>&int;</big>');
-
 LatexCmds.slash = bind(VanillaSymbol, '/');
 LatexCmds.vert = bind(VanillaSymbol,'|');
 LatexCmds.perp = LatexCmds.perpendicular = bind(VanillaSymbol,'\\perp ','&perp;');
@@ -302,6 +299,9 @@
 LatexCmds.xist = //LOL
 LatexCmds.xists = LatexCmds.exist = LatexCmds.exists =
   bind(VanillaSymbol,'\\exists ','&exist;');
+  
+LatexCmds.nexists = LatexCmds.nexist =
+      bind(VanillaSymbol, '\\nexists ', '&#8708;');
 
 LatexCmds.and = LatexCmds.land = LatexCmds.wedge =
   bind(VanillaSymbol,'\\wedge ','&and;');
@@ -319,7 +319,8 @@
 LatexCmds.cap = LatexCmds.intersect = LatexCmds.intersection =
   bind(BinaryOperator,'\\cap ','&cap;');
 
-LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'^\\circ ','&deg;');
+// FIXME: the correct LaTeX would be ^\circ but we can't parse that
+LatexCmds.deg = LatexCmds.degree = bind(VanillaSymbol,'\\degree ','&deg;');
 
 LatexCmds.ang = LatexCmds.angle = bind(VanillaSymbol,'\\angle ','&ang;');
 LatexCmds.measuredangle = bind(VanillaSymbol,'\\measuredangle ','&#8737;');
diff --git a/src/commands/math/commands.js b/src/commands/math/commands.js
index 37c00bf..414edaa 100644
--- a/src/commands/math/commands.js
+++ b/src/commands/math/commands.js
@@ -380,6 +380,10 @@
 LatexCmds.coprod =
 LatexCmds.coproduct = bind(SummationNotation,'\\coprod ','&#8720;');
 
+LatexCmds['∫'] =
+LatexCmds['int'] =
+LatexCmds.integral = bind(Symbol,'\\int ','<big>&int;</big>');
+
 var Fraction =
 LatexCmds.frac =
 LatexCmds.dfrac =
diff --git a/src/commands/text.js b/src/commands/text.js
index 42f85d4..d4352bd 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
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..fcaa23d 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 {
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/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..022dfda 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' +
diff --git a/src/services/keystroke.js b/src/services/keystroke.js
index 6ec672a..f3ec668 100644
--- a/src/services/keystroke.js
+++ b/src/services/keystroke.js
@@ -129,9 +129,6 @@
       while (cursor[L]) ctrlr.selectLeft();
       break;
 
-    case 'Enter':
-      return ctrlr.handle('enter');
-
     default:
       return;
     }
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/textarea.js b/src/services/textarea.js
index d178cbf..dd2d2de 100644
--- a/src/services/textarea.js
+++ b/src/services/textarea.js
@@ -86,6 +86,7 @@
     this.focusBlurEvents();
   };
   _.typedText = function(ch) {
+    if (ch === '\n') return this.handle('enter');
     var cursor = this.notify().cursor;
     cursor.parent.write(cursor, ch);
     this.scrollHoriz();
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/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/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();
+    }
+  });
 });